package com.marginallyclever.makelangeloRobot.loadAndSave; import java.awt.GridLayout; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.swing.JCheckBox; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.filechooser.FileNameExtensionFilter; import org.kabeja.dxf.Bounds; import org.kabeja.dxf.DXFConstants; import org.kabeja.dxf.DXFDocument; import org.kabeja.dxf.DXFEntity; import org.kabeja.dxf.DXFLWPolyline; import org.kabeja.dxf.DXFLayer; import org.kabeja.dxf.DXFLine; import org.kabeja.dxf.DXFPolyline; import org.kabeja.dxf.DXFSpline; import org.kabeja.dxf.DXFVertex; import org.kabeja.dxf.helpers.DXFSplineConverter; import org.kabeja.dxf.helpers.Point; import org.kabeja.parser.DXFParser; import org.kabeja.parser.ParseException; import org.kabeja.parser.Parser; import org.kabeja.parser.ParserBuilder; import com.marginallyclever.gcode.GCodeFile; import com.marginallyclever.makelangelo.Log; import com.marginallyclever.makelangelo.Makelangelo; import com.marginallyclever.makelangelo.Translator; import com.marginallyclever.makelangeloRobot.ImageManipulator; import com.marginallyclever.makelangeloRobot.MakelangeloRobot; /** * Reads in DXF file and converts it to a temporary gcode file, then calls LoadGCode. * @author Dan Royer * */ public class LoadAndSaveDXF extends ImageManipulator implements LoadAndSaveFileType { private static boolean shouldScaleOnLoad=true; private static boolean shouldInfillOnLoad=true; private static boolean shouldOptimizePathingOnLoad=false; private static FileNameExtensionFilter filter = new FileNameExtensionFilter(Translator.get("FileTypeDXF"), "dxf"); private double previousX,previousY; private double scale,imageCenterX,imageCenterY; private boolean writeNow; @Override public String getName() { return "DXF"; } @Override public FileNameExtensionFilter getFileNameFilter() { return filter; } @Override public boolean canLoad(String filename) { String ext = filename.substring(filename.lastIndexOf('.')); return (ext.equalsIgnoreCase(".dxf")); } @Override public boolean canSave(String filename) { String ext = filename.substring(filename.lastIndexOf('.')); return (ext.equalsIgnoreCase(".dxf")); } @Override public boolean load(InputStream in,MakelangeloRobot robot) { final JCheckBox checkScale = new JCheckBox(Translator.get("DXFScaleOnLoad")); final JCheckBox checkInfill = new JCheckBox(Translator.get("DXFInfillOnLoad")); final JCheckBox checkOptimize = new JCheckBox(Translator.get("DXFOptimizeOnLoad")); JPanel panel = new JPanel(new GridLayout(0, 1)); panel.add(checkScale); //panel.add(checkInfill); panel.add(checkOptimize); checkScale.setSelected(shouldScaleOnLoad); checkInfill.setSelected(shouldInfillOnLoad); checkOptimize.setSelected(shouldOptimizePathingOnLoad); int result = JOptionPane.showConfirmDialog(robot.getControlPanel(), panel, getName(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if (result == JOptionPane.OK_OPTION) { shouldScaleOnLoad = checkScale.isSelected(); shouldInfillOnLoad = checkInfill.isSelected(); shouldOptimizePathingOnLoad = checkOptimize.isSelected(); return loadNow(in,robot); } return false; } // count all entities in all layers @SuppressWarnings("unchecked") protected void countAllEntities(DXFDocument doc) { Iterator<DXFLayer> layerIter = (Iterator<DXFLayer>) doc.getDXFLayerIterator(); int entityTotal = 0; while (layerIter.hasNext()) { DXFLayer layer = (DXFLayer) layerIter.next(); int color = layer.getColor(); System.out.println("Found layer " + layer.getName() + "(RGB="+color+")"); Iterator<String> entityIter = (Iterator<String>) layer.getDXFEntityTypeIterator(); while (entityIter.hasNext()) { String entityType = (String) entityIter.next(); List<DXFEntity> entityList = (List<DXFEntity>) layer.getDXFEntities(entityType); System.out.println("Found " + entityList.size() + " of type " + entityType); entityTotal += entityList.size(); } } System.out.println(entityTotal + " total entities."); } /** * Put every entity into a bucket. * @param doc * @param grid * @param groups */ @SuppressWarnings("unchecked") protected void sortEntitiesIntoBucketsAndGroups(DXFDocument doc,DXFLayer layer,DXFBucketGrid grid,List<DXFGroup> groups) { Log.message("Sorting layer "+layer.getName()+" into buckets..."); Iterator<String> entityTypeIter = (Iterator<String>) layer.getDXFEntityTypeIterator(); while (entityTypeIter.hasNext()) { String entityType = (String) entityTypeIter.next(); List<DXFEntity> entityList = (List<DXFEntity>)layer.getDXFEntities(entityType); Iterator<DXFEntity> iter = entityList.iterator(); while(iter.hasNext()) { DXFEntity e = iter.next(); DXFBucketEntity be = new DXFBucketEntity(e); if (e.getType().equals(DXFConstants.ENTITY_TYPE_LINE)) { DXFLine line = (DXFLine)e; grid.addEntity(be, line.getStartPoint()); grid.addEntity(be, line.getEndPoint()); continue; } if(e.getType().equals(DXFConstants.ENTITY_TYPE_SPLINE)) { e = DXFSplineConverter.toDXFPolyline((DXFSpline)e); // fall through to the next case, polyline. } if(e.getType().equals(DXFConstants.ENTITY_TYPE_POLYLINE)) { DXFPolyline polyLine = (DXFPolyline)e; if(!polyLine.isClosed()) { grid.addEntity(be, polyLine.getVertex(0).getPoint()); grid.addEntity(be, polyLine.getVertex(polyLine.getVertexCount()-1).getPoint()); } else { grid.addEntity(be, polyLine.getVertex(0).getPoint()); grid.addEntity(be, polyLine.getVertex(0).getPoint()); } continue; } if(e.getType().equals(DXFConstants.ENTITY_TYPE_LWPOLYLINE)) { DXFLWPolyline polyLine = (DXFLWPolyline)e; if(!polyLine.isClosed()) { grid.addEntity(be, polyLine.getVertex(0).getPoint()); grid.addEntity(be, polyLine.getVertex(polyLine.getVertexCount()-1).getPoint()); } else { grid.addEntity(be, polyLine.getVertex(0).getPoint()); grid.addEntity(be, polyLine.getVertex(0).getPoint()); } continue; } //if(e.getType().equals(DXFConstants.ENTITY_TYPE_ARC)) {} //if(e.getType().equals(DXFConstants.ENTITY_TYPE_CIRCLE)) {} // I don't know this entity type. Log.error("Unknown DXF type "+e.getType()); } } grid.countEntitiesInBuckets(); } /** * * @param in * @param robot * @return true if load is successful. */ @SuppressWarnings("unchecked") private boolean loadNow(InputStream in,MakelangeloRobot robot) { Log.message(Translator.get("FileTypeDXF2")+"..."); File tempFile; try { tempFile = File.createTempFile("temp", ".ngc"); } catch (IOException e1) { e1.printStackTrace(); return false; } tempFile.deleteOnExit(); Log.message(Translator.get("Converting") + " " + tempFile.getName()); // Read in the DXF file Parser parser = ParserBuilder.createDefaultParser(); try { parser.parse(in, DXFParser.DEFAULT_ENCODING); } catch (ParseException e) { e.printStackTrace(); return false; } DXFDocument doc = parser.getDocument(); Bounds bounds = doc.getBounds(); imageCenterX = (bounds.getMaximumX() + bounds.getMinimumX()) / 2.0; imageCenterY = (bounds.getMaximumY() + bounds.getMinimumY()) / 2.0; // find the scale to fit the image on the paper without // altering the aspect ratio double imageWidth = (bounds.getMaximumX() - bounds.getMinimumX()); double imageHeight = (bounds.getMaximumY() - bounds.getMinimumY()); double paperHeight = robot.getSettings().getPaperHeight() * 10.0 * robot.getSettings().getPaperMargin(); double paperWidth = robot.getSettings().getPaperWidth () * 10.0 * robot.getSettings().getPaperMargin(); scale = 1; if(shouldScaleOnLoad) { double innerAspectRatio = imageWidth / imageHeight; double outerAspectRatio = paperWidth / paperHeight; scale = (innerAspectRatio >= outerAspectRatio) ? (paperWidth / imageWidth) : (paperHeight / imageHeight); } //countAllEntities(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile); Writer out = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) { // prepare for exporting machine = robot.getSettings(); double toolDiameterSquared = Math.pow(machine.getDiameter()/2, 2); double toolMinimumStepSize = Math.pow(machine.getDiameter(), 2); setAbsoluteMode(out); previousX = machine.getHomeX(); previousY = machine.getHomeY(); // convert each entity Iterator<DXFLayer> layerIter = doc.getDXFLayerIterator(); while (layerIter.hasNext()) { DXFLayer layer = (DXFLayer) layerIter.next(); int color = layer.getColor(); System.out.println("Found layer " + layer.getName() + "(RGB="+color+")"); // Some DXF layers are empty. Only write the tool change command if there's something on this layer. Iterator<String> entityTypeIter = (Iterator<String>) layer.getDXFEntityTypeIterator(); if (entityTypeIter.hasNext()) { layer.getColor(); machine.writeChangeTo(out,layer.getName()); } // Sort the entities on this layer into the buckets. // Buckets are arranged in an XY grid. // All non-closed entities would appear in two buckets. // One Entity might be in the same bucket twice. Point topLeft = new Point(); Point bottomRight = new Point(); topLeft.setX(bounds.getMinimumX()); topLeft.setY(bounds.getMinimumY()); bottomRight.setX(bounds.getMaximumX()); bottomRight.setY(bounds.getMaximumY()); DXFBucketGrid grid = new DXFBucketGrid(15,15,topLeft,bottomRight); List<DXFGroup> groups = new LinkedList<DXFGroup>(); sortEntitiesIntoBucketsAndGroups(doc,layer,grid,groups); //DXFGroup infillGroup=null; if(shouldInfillOnLoad) { //infillGroup = infillClosedAreas(layer); } if(shouldOptimizePathingOnLoad) { // Use the buckets to narrow the search field and find neighboring entities grid.sortEntitiesIntoContinguousGroups(groups); } else { grid.dumpEverythingIntoABucket(groups); } removeDuplicates(groups); // We have a list of groups. // Each group is set of lines that make a continuous path. // Maybe even a closed path! // Some of the lines in each group may be flipped. // TODO sort out the flips so the end of line N is the start of line N+1. // TODO detect which groups are closed. // TODO fill in the closed groups if the user says ok. //if(infillGroup!=null) { // groups.add(infillGroup); //} Iterator<DXFGroup> groupIter = groups.iterator(); while(groupIter.hasNext()) { DXFGroup g = groupIter.next(); // but each separate line might be forwards or backwards. // scan backward through the group to find the starting of the first line (previousX,previousY). writeNow=false; Iterator<DXFBucketEntity> ents = g.entities.descendingIterator(); while(ents.hasNext()) { DXFBucketEntity e = ents.next(); parseEntity(e.entity,out,toolDiameterSquared,toolMinimumStepSize); } // move to the start and put the pen down liftPen(out); moveTo(out,previousX,previousY,true); lowerPen(out); // work forward through the loop, writing as you go. writeNow=true; ents = g.entities.iterator(); while(ents.hasNext()) { DXFBucketEntity e = ents.next(); parseEntity(e.entity,out,toolDiameterSquared,toolMinimumStepSize); } } } // entities finished. Close up file. liftPen(out); moveTo(out, (float)machine.getHomeX(), (float)machine.getHomeY(),true); out.flush(); out.close(); Log.message("Done!"); LoadAndSaveGCode loader = new LoadAndSaveGCode(); InputStream fileInputStream = new FileInputStream(tempFile); loader.load(fileInputStream,robot); } catch (IOException e) { e.printStackTrace(); } tempFile.delete(); return true; } protected void parseEntity(DXFEntity e,Writer out,double toolDiameterSquared,double toolMinimumStepSize) throws IOException { if (e.getType().equals(DXFConstants.ENTITY_TYPE_LINE)) { parseDXFLine(out,(DXFLine)e,toolDiameterSquared); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_SPLINE)) { parseDXFPolyline(out,DXFSplineConverter.toDXFPolyline((DXFSpline)e),toolMinimumStepSize); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_POLYLINE)) { parseDXFPolyline(out,(DXFPolyline)e,toolMinimumStepSize); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_LWPOLYLINE)) { parseDXFLWPolyline(out,(DXFLWPolyline)e,toolMinimumStepSize); } } protected double distanceFromPrevious(Point p) { double dx = previousX - p.getX(); double dy = previousY - p.getY(); return dx*dx+dy*dy; } protected Point getEntityStart(DXFEntity e) { if (e.getType().equals(DXFConstants.ENTITY_TYPE_LINE)) { DXFLine line = (DXFLine)e; return line.getStartPoint(); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_SPLINE)) { DXFPolyline line = DXFSplineConverter.toDXFPolyline((DXFSpline)e); DXFVertex v = line.getVertex(0); return new Point(v.getX(),v.getY(),v.getZ()); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_POLYLINE)) { DXFPolyline line = (DXFPolyline)e; DXFVertex v = line.getVertex(0); return new Point(v.getX(),v.getY(),v.getZ()); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_LWPOLYLINE)) { DXFLWPolyline line = (DXFLWPolyline)e; DXFVertex v = line.getVertex(0); return new Point(v.getX(),v.getY(),v.getZ()); } assert(false); return null; } protected Point getEntityEnd(DXFEntity e) { if (e.getType().equals(DXFConstants.ENTITY_TYPE_LINE)) { DXFLine line = (DXFLine)e; return line.getEndPoint(); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_SPLINE)) { DXFPolyline line = DXFSplineConverter.toDXFPolyline((DXFSpline)e); int n=0; if(!line.isClosed()) n=line.getVertexCount()-1; DXFVertex v = line.getVertex(n); return new Point(v.getX(),v.getY(),v.getZ()); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_POLYLINE)) { DXFPolyline line = (DXFPolyline)e; int n=0; if(!line.isClosed()) n=line.getVertexCount()-1; DXFVertex v = line.getVertex(n); return new Point(v.getX(),v.getY(),v.getZ()); } else if (e.getType().equals(DXFConstants.ENTITY_TYPE_LWPOLYLINE)) { DXFLWPolyline line = (DXFLWPolyline)e; int n=0; if(!line.isClosed()) n=line.getVertexCount()-1; DXFVertex v = line.getVertex(n); return new Point(v.getX(),v.getY(),v.getZ()); } assert(false); return null; } /** * http://stackoverflow.com/questions/203984/how-do-i-remove-repeated-elements-from-arraylist * @param groups */ protected void removeDuplicates(List<DXFGroup> groups) { int totalRemoved=0; Iterator<DXFGroup> g = groups.iterator(); while(g.hasNext()) { DXFGroup group = g.next(); int before = group.entities.size(); Set<DXFBucketEntity> hs = new LinkedHashSet<>(); hs.addAll(group.entities); group.entities.clear(); group.entities.addAll(hs); int after = group.entities.size(); totalRemoved += before - after; } if(totalRemoved!=0) System.out.println(totalRemoved+" duplicates removed."); } protected void parseDXFLine(Writer out,DXFLine entity,double toolDiameterSquared) throws IOException { Point start = entity.getStartPoint(); Point end = entity.getEndPoint(); double x = (start.getX() - imageCenterX) * scale; double y = (start.getY() - imageCenterY) * scale; double x2 = (end.getX() - imageCenterX) * scale; double y2 = (end.getY() - imageCenterY) * scale; // skip extremely short lines? if( Math.abs(x-x2)<0.001 && Math.abs(y-y2)<0.001) { return; } // which end is closer to the previous (x,y) ? double dx = previousX - x; double dy = previousY - y; double dx2 = previousX - x2; double dy2 = previousY - y2; if ( dx * dx + dy * dy < dx2 * dx2 + dy2 * dy2 ) { parseDXFLineEnds(out,toolDiameterSquared,x,y,x2,y2); } else { parseDXFLineEnds(out,toolDiameterSquared,x2,y2,x,y); } } protected void parseDXFPolyline(Writer out,DXFPolyline entity,double toolDiameterSquared) throws IOException { if(entity.isClosed()) { // only one end to care about parseDXFPolylineForward(out,entity,toolDiameterSquared); } else { // which end is closest to the previous (x,y)? int n = entity.getVertexCount()-1; double x = (entity.getVertex(0).getX() - imageCenterX) * scale; double y = (entity.getVertex(0).getY() - imageCenterY) * scale; double x2 = (entity.getVertex(n).getX() - imageCenterX) * scale; double y2 = (entity.getVertex(n).getY() - imageCenterY) * scale; // which end is closer to the previous (x,y) ? double dx = previousX - x; double dy = previousY - y; double dx2 = previousX - x2; double dy2 = previousY - y2; if ( dx * dx + dy * dy < dx2 * dx2 + dy2 * dy2 ) { // first point is closer parseDXFPolylineForward(out,entity,toolDiameterSquared); } else { // last point is closer parseDXFPolylineBackward(out,entity,toolDiameterSquared); } } } protected void parseDXFLWPolylineForward(Writer out,DXFLWPolyline entity,double toolDiameterSquared) throws IOException { boolean first = true; int count = entity.getVertexCount() + (entity.isClosed()?1:0); for (int j = 0; j < count; ++j) { DXFVertex v = entity.getVertex(j % entity.getVertexCount()); double x = (v.getX() - imageCenterX) * scale; double y = (v.getY() - imageCenterY) * scale; parseCurvingLine(out,x,y,toolDiameterSquared,first,j<count-1); first = false; } } protected void parseDXFLWPolylineBackward(Writer out,DXFLWPolyline entity,double toolDiameterSquared) throws IOException { boolean first = true; int count = entity.getVertexCount() + (entity.isClosed()?1:0); for (int j = 0; j < count; ++j) { DXFVertex v = entity.getVertex((count+count-1-j) % entity.getVertexCount()); double x = (v.getX() - imageCenterX) * scale; double y = (v.getY() - imageCenterY) * scale; parseCurvingLine(out,x,y,toolDiameterSquared,first,j<count-1); first = false; } } protected void parseDXFLWPolyline(Writer out,DXFLWPolyline entity,double toolDiameterSquared) throws IOException { if(entity.isClosed()) { // only one end to care about parseDXFLWPolylineForward(out,entity,toolDiameterSquared); } else { // which end is closest to the previous (x,y)? int n = entity.getVertexCount()-1; double x = (entity.getVertex(0).getX() - imageCenterX) * scale; double y = (entity.getVertex(0).getY() - imageCenterY) * scale; double x2 = (entity.getVertex(n).getX() - imageCenterX) * scale; double y2 = (entity.getVertex(n).getY() - imageCenterY) * scale; // which end is closer to the previous (x,y) ? double dx = previousX - x; double dy = previousY - y; double dx2 = previousX - x2; double dy2 = previousY - y2; if ( dx * dx + dy * dy < dx2 * dx2 + dy2 * dy2 ) { // first point is closer parseDXFLWPolylineForward(out,entity,toolDiameterSquared); } else { // last point is closer parseDXFLWPolylineBackward(out,entity,toolDiameterSquared); } } } protected void parseDXFPolylineForward(Writer out,DXFPolyline entity,double toolDiameterSquared) throws IOException { boolean first = true; int count = entity.getVertexCount() + (entity.isClosed()?1:0); for (int j = 0; j < count; ++j) { DXFVertex v = entity.getVertex(j % entity.getVertexCount()); double x = (v.getX() - imageCenterX) * scale; double y = (v.getY() - imageCenterY) * scale; parseCurvingLine(out,x,y,toolDiameterSquared,first,j<count-1); first = false; } } protected void parseDXFPolylineBackward(Writer out,DXFPolyline entity,double toolDiameterSquared) throws IOException { boolean first = true; int count = entity.getVertexCount() + (entity.isClosed()?1:0); for (int j = 0; j < count; ++j) { DXFVertex v = entity.getVertex((count+count-1-j) % entity.getVertexCount()); double x = (v.getX() - imageCenterX) * scale; double y = (v.getY() - imageCenterY) * scale; parseCurvingLine(out,x,y,toolDiameterSquared,first,j<count-1); first = false; } } protected void parseCurvingLine(Writer out,double x,double y,double limitSquared,boolean first,boolean notLast) throws IOException { double dx = x - previousX; double dy = y - previousY; if (first == true) { if (dx * dx + dy * dy > limitSquared) { // line does not start at last tool location, lift and move. if(writeNow){ liftPen(out); moveTo(out, (float) x, (float) y, true); } previousX = x; previousY = y; } // else line starts right here, pen is down, do nothing extra. } else { if(writeNow) { lowerPen(out); } // not the first point, draw. if (notLast && dx * dx + dy * dy < limitSquared) return; // points too close together if(writeNow) { moveTo(out, (float) x, (float) y, false); } previousX = x; previousY = y; } } protected void parseDXFLineEnds(Writer out,double limitSquared,double x,double y,double x2,double y2) throws IOException { double dx = x - previousX; double dy = y - previousY; if (dx * dx + dy * dy > limitSquared) { if(writeNow){ liftPen(out); moveTo(out, (float) x, (float) y, true); } previousX = x; previousY = y; } if(writeNow){ lowerPen(out); moveTo(out, (float) x2, (float) y2, false); } previousX = x2; previousY = y2; } @Override /** * see http://paulbourke.net/dataformats/dxf/min3d.html for details * @param outputStream where to write the data * @param robot the robot from which the data is obtained * @return true if save succeeded. */ public boolean save(OutputStream outputStream, MakelangeloRobot robot) { Log.message("saving..."); GCodeFile sourceMaterial = robot.gCode; sourceMaterial.setLinesProcessed(0); OutputStreamWriter out = new OutputStreamWriter(outputStream); try { // header out.write("999\nDXF created by Makelangelo software v"+Makelangelo.VERSION+"\n"); out.write("0\nSECTION\n"); out.write("2\nHEADER\n"); out.write("9\n$ACADVER\n1\nAC1006\n"); out.write("9\n$INSBASE\n"); out.write("10\n"+robot.getSettings().getPaperLeft()+"\n"); out.write("20\n"+robot.getSettings().getPaperBottom()+"\n"); out.write("30\n0.0\n"); out.write("9\n$EXTMIN\n"); out.write("10\n"+robot.getSettings().getPaperLeft()+"\n"); out.write("20\n"+robot.getSettings().getPaperBottom()+"\n"); out.write("30\n0.0\n"); out.write("9\n$EXTMAX\n"); out.write("10\n"+robot.getSettings().getPaperRight()+"\n"); out.write("20\n"+robot.getSettings().getPaperTop()+"\n"); out.write("30\n0.0\n"); out.write("0\nENDSEC\n"); // tables section out.write("0\nSECTION\n"); out.write("2\nTABLES\n"); // line type out.write("0\nTABLE\n"); out.write("2\nLTYPE\n"); out.write("70\n1\n"); out.write("0\nLTYPE\n"); out.write("2\nCONTINUOUS\n"); out.write("70\n64\n"); out.write("3\nSolid line\n"); out.write("72\n65\n"); out.write("73\n0\n"); out.write("40\n0.000\n"); out.write("0\nENDTAB\n"); // layers out.write("0\nTABLE\n"); out.write("2\nLAYER\n"); out.write("70\n6\n"); out.write("0\nLAYER\n"); out.write("2\n1\n"); out.write("70\n64\n"); out.write("62\n7\n"); out.write("6\nCONTINUOUS\n"); out.write("0\nLAYER\n"); out.write("2\n2\n"); out.write("70\n64\n"); out.write("62\n7\n"); out.write("6\nCONTINUOUS\n"); out.write("0\nENDTAB\n"); out.write("0\nTABLE\n"); out.write("2\nSTYLE\n"); out.write("70\n0\n"); out.write("0\nENDTAB\n"); // end tables out.write("0\nENDSEC\n"); // empty blocks section (good form?) out.write("0\nSECTION\n"); out.write("0\nBLOCKS\n"); out.write("0\nENDSEC\n"); // now the lines out.write("0\nSECTION\n"); out.write("2\nENTITIES\n"); boolean penUp=true; float x0 = (float) robot.getSettings().getHomeX(); float y0 = (float) robot.getSettings().getHomeY(); float x1; float y1; String matchUp = robot.getSettings().getPenUpString(); String matchDown = robot.getSettings().getPenDownString(); if(matchUp.contains(";")) { matchUp = matchUp.substring(0, matchUp.indexOf(";")); } if(matchDown.contains(";")) { matchDown = matchDown.substring(0, matchDown.indexOf(";")); } int total=sourceMaterial.getLinesTotal(); Log.message(total+" total lines to save."); for(int i=0;i<total;++i) { String str = sourceMaterial.nextLine(); // trim comments if(str.contains(";")) { str = str.substring(0, str.indexOf(";")); } if(str.contains(matchUp)) { penUp=true; } if(str.contains(matchDown)) { penUp=false; } if(str.startsWith("G0") || str.startsWith("G1")) { // move command String[] tokens = str.split(" "); x1=x0; y1=y0; int j; for(j=0;j<tokens.length;++j) { String tok = tokens[j]; if(tok.startsWith("X")) { x1=Float.parseFloat(tok.substring(1)); } else if(tok.startsWith("Y")) { y1=Float.parseFloat(tok.substring(1)); } } if(penUp==false && ( x1!=x0 || y1!=y0 ) ) { out.write("0\nLINE\n"); out.write("8\n1\n"); // layer 1 out.write("10\n"+x0+"\n"); out.write("20\n"+y0+"\n"); out.write("11\n"+x1+"\n"); out.write("21\n"+y1+"\n"); } x0=x1; y0=y1; } } // wrap it up out.write("0\nENDSEC\n"); out.write("0\nEOF\n"); out.flush(); } catch(IOException e) { Log.error(Translator.get("SaveError") +" "+ e.getLocalizedMessage()); return false; } Log.message("done."); return true; } @Override public boolean canLoad() { return true; } @Override public boolean canSave() { return true; } }