├── .classpath ├── .project ├── README ├── build.xml └── src └── daid └── sliceAndDaid ├── Layer.java ├── LayerPart.java ├── Model.java ├── Polygon.java ├── Segment2D.java ├── SliceAndDaidMain.java ├── config ├── CraftConfig.java ├── CraftConfigLoader.java └── Setting.java ├── tool ├── GCodeTool.java ├── PathTool.java ├── PerimeterTool.java ├── SliceTool.java └── SpeedTool.java ├── ui ├── ConfigWindow.java ├── LogWindow.java └── PreviewFrame.java └── util ├── AABBTree.java ├── AABBrect.java ├── GCodeFile.java ├── Logger.java ├── LoggingInterface.java ├── Triangle.java ├── Vector2.java └── Vector3.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SliceAndDaid 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | THIS PROJECT IS LONG DEAD AND REPLACED BY CURA, kept for historical reasons! 2 | 3 | 4 | 5 | 6 | WHAT IS IT? 7 | =========== 8 | SliceAndDaid is a 3D slicer written in Java. It's a much faster replacement 9 | for skeinforge. 10 | 11 | It processes STL input files into GCode. Right now it only creates the outline. 12 | It does NOT do infills yet. Which will be the next step. 13 | 14 | However, it currently does so in seconds instead of minutes compared to Skeinforge. 15 | 16 | HOW TO BUILD? 17 | ============= 18 | I've provided a simple ant build script. 19 | So running with ant installed should be a simple: 20 | $ ant 21 | $ java -jar SliceAndDaid.jar 22 | 23 | There are no extra dependences, just java. Eclipse project files are also supplied. 24 | 25 | HOW TO USE? 26 | =========== 27 | Change the settings you want, and slice ahead. The default settings should work 28 | on an Ultimaker. These settings are conservative. 29 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/Layer.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import java.util.HashSet; 4 | import java.util.Vector; 5 | 6 | import daid.sliceAndDaid.config.CraftConfig; 7 | import daid.sliceAndDaid.util.AABBTree; 8 | import daid.sliceAndDaid.util.AABBrect; 9 | import daid.sliceAndDaid.util.Logger; 10 | 11 | public class Layer 12 | { 13 | public int layerNr; 14 | 15 | public Vector modelSegmentList = new Vector(); 16 | 17 | public Segment2D pathStart; 18 | public LayerPart skirt = null; 19 | public LayerPart modelPart = new LayerPart(this); 20 | public LayerPart[] outlinePart; 21 | private AABBTree modelSegmentTree = new AABBTree(); 22 | 23 | public Layer(int layerNr, double minX, double minY, double maxX, double maxY) 24 | { 25 | this.layerNr = layerNr; 26 | this.outlinePart = new LayerPart[CraftConfig.perimeterCount]; 27 | } 28 | 29 | public void addModelSegment(Segment2D segment) 30 | { 31 | if (segment.start.asGoodAsEqual(segment.end)) 32 | return; 33 | modelSegmentTree.insert(segment); 34 | modelSegmentList.add(segment); 35 | } 36 | 37 | private void removeModelSegment(Segment2D segment) 38 | { 39 | modelSegmentList.remove(segment); 40 | modelSegmentTree.remove(segment); 41 | } 42 | 43 | public boolean optimize() 44 | { 45 | // Link up the segments with start/ends, so polygons are created. 46 | for (Segment2D s1 : modelSegmentList) 47 | { 48 | if (s1.getPrev() == null) 49 | { 50 | Segment2D best = null; 51 | double bestDist2 = 0.01; 52 | for (Segment2D s2 : modelSegmentTree.query(new AABBrect(s1.start, s1.start))) 53 | { 54 | if (s1 != s2 && s2.getNext() == null && s1.start.asGoodAsEqual(s2.end) && s1.start.sub(s2.end).vSize2() < bestDist2) 55 | { 56 | best = s2; 57 | bestDist2 = s1.start.sub(s2.end).vSize2(); 58 | break; 59 | } 60 | } 61 | if (best != null) 62 | { 63 | s1.start = best.end; 64 | best.setNext(s1); 65 | } 66 | } 67 | if (s1.getNext() == null) 68 | { 69 | Segment2D best = null; 70 | double bestDist2 = 0.01; 71 | for (Segment2D s2 : modelSegmentTree.query(new AABBrect(s1.end, s1.end))) 72 | { 73 | if (s1 != s2 && s2.getPrev() == null && s1.end.asGoodAsEqual(s2.start) && s1.end.sub(s2.start).vSize2() < bestDist2) 74 | { 75 | best = s2; 76 | bestDist2 = s1.end.sub(s2.start).vSize2(); 77 | break; 78 | } 79 | } 80 | if (best != null) 81 | { 82 | s1.end = best.start; 83 | s1.setNext(best); 84 | } 85 | } 86 | } 87 | 88 | for (Segment2D s : modelSegmentList) 89 | { 90 | if (s.getPrev() != null && s.getPrev().getNext() != s) 91 | throw new RuntimeException(); 92 | if (s.getNext() != null && s.getNext().getPrev() != s) 93 | throw new RuntimeException(); 94 | if (s.getNext() != null && !modelSegmentList.contains(s.getNext())) 95 | throw new RuntimeException(); 96 | if (s.getPrev() != null && !modelSegmentList.contains(s.getPrev())) 97 | throw new RuntimeException(); 98 | } 99 | 100 | boolean manifoldErrorReported = false; 101 | HashSet tmpSet = new HashSet(modelSegmentList); 102 | while (tmpSet.size() > 0) 103 | { 104 | Segment2D start = tmpSet.iterator().next(); 105 | boolean manifold = false; 106 | for (Segment2D s = start; s != null; s = s.getNext()) 107 | { 108 | if (!tmpSet.contains(s)) 109 | { 110 | Logger.warning("Problem in layer: " + layerNr + "\nTried to create a segment link from links that where already used..."); 111 | break; 112 | } 113 | if (s.getNext() == start) 114 | { 115 | manifold = true; 116 | break; 117 | } 118 | } 119 | if (manifold) 120 | { 121 | Polygon poly = new Polygon(start); 122 | for (Segment2D s : poly) 123 | { 124 | tmpSet.remove(s); 125 | } 126 | addModelPolygon(poly); 127 | } else 128 | { 129 | if (!manifoldErrorReported) 130 | Logger.warning("Object not manifold in layer: " + layerNr); 131 | manifoldErrorReported = true; 132 | for (Segment2D s = start; s != null; s = s.getNext()) 133 | { 134 | tmpSet.remove(s); 135 | s.setType(Segment2D.TYPE_ERROR); 136 | if (s.getNext() == start) 137 | break; 138 | } 139 | for (Segment2D s = start; s != null; s = s.getPrev()) 140 | { 141 | tmpSet.remove(s); 142 | s.setType(Segment2D.TYPE_ERROR); 143 | if (s.getPrev() == start) 144 | break; 145 | } 146 | } 147 | } 148 | return manifoldErrorReported; 149 | } 150 | 151 | private void addModelPolygon(Polygon poly) 152 | { 153 | for (Segment2D s : poly) 154 | { 155 | if (s.getNormal().dot(s.getNext().getNormal()) > CraftConfig.joinMinCosAngle) 156 | { 157 | removeModelSegment(s); 158 | Segment2D next = s.getNext(); 159 | modelSegmentTree.remove(next); 160 | poly.remove(s); 161 | modelSegmentTree.insert(next); 162 | } 163 | } 164 | modelPart.addPolygon(poly); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/LayerPart.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import java.util.Iterator; 4 | import java.util.Vector; 5 | 6 | import daid.sliceAndDaid.util.AABBTree; 7 | import daid.sliceAndDaid.util.AABBrect; 8 | import daid.sliceAndDaid.util.Vector2; 9 | 10 | public class LayerPart implements Iterable 11 | { 12 | private Layer layer; 13 | private Vector polygons = new Vector(); 14 | 15 | public LayerPart(Layer layer) 16 | { 17 | this.layer = layer; 18 | } 19 | 20 | public LayerPart(LayerPart layerPart) 21 | { 22 | this.layer = layerPart.layer; 23 | } 24 | 25 | public Polygon getLargestPolygon() 26 | { 27 | Polygon largestPoly = null; 28 | double largestPolySize = 0; 29 | for (Polygon poly : polygons) 30 | { 31 | AABBrect polygonRect = poly.getAABB(); 32 | 33 | if (polygonRect.getPerimeter() > largestPolySize) 34 | { 35 | largestPolySize = polygonRect.getPerimeter(); 36 | largestPoly = poly; 37 | } 38 | } 39 | return largestPoly; 40 | } 41 | 42 | public void addPolygon(Polygon poly) 43 | { 44 | poly.check(); 45 | if (poly.empty()) 46 | { 47 | return; 48 | } 49 | polygons.add(poly); 50 | } 51 | 52 | /** 53 | * makeConvex is used to generate a single convex polygon from the existing polygon set. 54 | * 55 | * This is used for the skirt. Right now it creates a square box around the object. 56 | */ 57 | public LayerPart makeConvex() 58 | { 59 | LayerPart ret = new LayerPart(this); 60 | double minX = Double.MAX_VALUE; 61 | double maxX = Double.MIN_VALUE; 62 | double minY = Double.MAX_VALUE; 63 | double maxY = Double.MIN_VALUE; 64 | for (Polygon p : polygons) 65 | { 66 | for (Segment2D s : p) 67 | { 68 | if (s.start.x < minX) 69 | minX = s.start.x; 70 | if (s.start.y < minY) 71 | minY = s.start.y; 72 | if (s.start.x > maxX) 73 | maxX = s.start.x; 74 | if (s.start.y > maxY) 75 | maxY = s.start.y; 76 | } 77 | } 78 | Polygon p = new Polygon(); 79 | p.addEnd(new Segment2D(Segment2D.TYPE_PERIMETER, new Vector2(minX, minY), new Vector2(minX, maxY))); 80 | p.addEnd(new Segment2D(Segment2D.TYPE_PERIMETER, new Vector2(minX, maxY), new Vector2(maxX, maxY))); 81 | p.addEnd(new Segment2D(Segment2D.TYPE_PERIMETER, new Vector2(maxX, maxY), new Vector2(maxX, minY))); 82 | p.addEnd(new Segment2D(Segment2D.TYPE_PERIMETER, new Vector2(maxX, minY), new Vector2(minX, minY))); 83 | p.close(); 84 | ret.addPolygon(p); 85 | return ret; 86 | } 87 | 88 | public Iterator iterator() 89 | { 90 | return polygons.iterator(); 91 | } 92 | 93 | public Vector getPolygonListClone() 94 | { 95 | return new Vector(polygons); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/Model.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.util.Vector; 8 | 9 | import daid.sliceAndDaid.util.Logger; 10 | import daid.sliceAndDaid.util.Triangle; 11 | import daid.sliceAndDaid.util.Vector3; 12 | 13 | public class Model 14 | { 15 | public Vector triangles; 16 | 17 | public Model(String filename) throws IOException 18 | { 19 | Logger.updateStatus("Loading: " + filename); 20 | 21 | if (filename.toLowerCase().endsWith(".stl")) 22 | { 23 | char[] buf = new char[5]; 24 | BufferedReader br = new BufferedReader(new FileReader(filename)); 25 | br.mark(5); 26 | br.read(buf); 27 | br.close(); 28 | String header = new String(buf); 29 | 30 | if (header.equals("solid")) 31 | readAsciiSTL(filename); 32 | else 33 | readBinarySTL(filename); 34 | } else 35 | { 36 | new RuntimeException("Unknown model format: " + filename); 37 | } 38 | Logger.message("Triangle count: " + triangles.size()); 39 | } 40 | 41 | public Vector3 getMin() 42 | { 43 | Vector3 ret = new Vector3(); 44 | ret.x = Double.MAX_VALUE; 45 | ret.y = Double.MAX_VALUE; 46 | ret.z = Double.MAX_VALUE; 47 | for (Triangle t : triangles) 48 | { 49 | for (int i = 0; i < 3; i++) 50 | { 51 | if (ret.x > t.point[i].x) 52 | ret.x = t.point[i].x; 53 | if (ret.y > t.point[i].y) 54 | ret.y = t.point[i].y; 55 | if (ret.z > t.point[i].z) 56 | ret.z = t.point[i].z; 57 | } 58 | } 59 | return ret; 60 | } 61 | 62 | public Vector3 getMax() 63 | { 64 | Vector3 ret = new Vector3(); 65 | ret.x = Double.MIN_VALUE; 66 | ret.y = Double.MIN_VALUE; 67 | ret.z = Double.MIN_VALUE; 68 | for (Triangle t : triangles) 69 | { 70 | for (int i = 0; i < 3; i++) 71 | { 72 | if (ret.x < t.point[i].x) 73 | ret.x = t.point[i].x; 74 | if (ret.y < t.point[i].y) 75 | ret.y = t.point[i].y; 76 | if (ret.z < t.point[i].z) 77 | ret.z = t.point[i].z; 78 | } 79 | } 80 | return ret; 81 | } 82 | 83 | private void readBinarySTL(String filename) throws IOException 84 | { 85 | RandomAccessFile raf = new RandomAccessFile(filename, "r"); 86 | byte[] header = new byte[80]; 87 | raf.read(header); 88 | int triangleCount = Integer.reverseBytes(raf.readInt()); 89 | triangles = new Vector(); 90 | for (int i = 0; i < triangleCount; i++) 91 | { 92 | Logger.setProgress(i, triangleCount); 93 | for (int j = 0; j < 3; j++) 94 | raf.readFloat(); 95 | 96 | Triangle t = new Triangle(); 97 | float x = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 98 | float y = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 99 | float z = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 100 | t.point[0] = new Vector3(x, y, z); 101 | x = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 102 | y = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 103 | z = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 104 | t.point[1] = new Vector3(x, y, z); 105 | x = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 106 | y = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 107 | z = Float.intBitsToFloat(Integer.reverseBytes(raf.readInt())); 108 | t.point[2] = new Vector3(x, y, z); 109 | raf.readShort();// flags 110 | triangles.add(t); 111 | } 112 | System.out.println(getMin()); 113 | } 114 | 115 | private void readAsciiSTL(String filename) throws IOException 116 | { 117 | BufferedReader br = new BufferedReader(new FileReader(filename)); 118 | String line; 119 | int i = 0; 120 | Vector3 normal = null; 121 | Triangle nextTri = new Triangle(); 122 | triangles = new Vector(); 123 | while ((line = br.readLine()) != null) 124 | { 125 | line = line.trim(); 126 | if (line.startsWith("facet normal")) 127 | { 128 | String[] parts = line.split(" +"); 129 | normal = new Vector3(Double.parseDouble(parts[2]), Double.parseDouble(parts[3]), Double.parseDouble(parts[4])); 130 | } 131 | if (line.startsWith("vertex")) 132 | { 133 | String[] parts = line.split(" +"); 134 | nextTri.point[i] = new Vector3(Double.parseDouble(parts[1]), Double.parseDouble(parts[2]), Double.parseDouble(parts[3])); 135 | i++; 136 | if (i == 3) 137 | { 138 | if (normal.vSize2() > 0.1 && nextTri.getNormal().dot(normal) < 0.5) 139 | { 140 | // Triangle winding order and normal don't point in the same direction... 141 | // Flip the triangle? 142 | } 143 | triangles.add(nextTri); 144 | nextTri = new Triangle(); 145 | i = 0; 146 | } 147 | } 148 | } 149 | br.close(); 150 | } 151 | 152 | public void center() 153 | { 154 | Vector3 min = getMin(); 155 | Vector3 max = getMax(); 156 | Vector3 translate = new Vector3(); 157 | translate.z = -min.z; 158 | translate.x = -(max.x + min.x) / 2; 159 | translate.y = -(max.y + min.y) / 2; 160 | 161 | move(translate); 162 | } 163 | 164 | private void move(Vector3 translate) 165 | { 166 | for (Triangle t : triangles) 167 | { 168 | for (int i = 0; i < 3; i++) 169 | { 170 | t.point[i].addToSelf(translate); 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/Polygon.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import java.util.Iterator; 4 | 5 | import daid.sliceAndDaid.util.AABBrect; 6 | import daid.sliceAndDaid.util.Vector2; 7 | 8 | public class Polygon implements Iterable 9 | { 10 | private Segment2D first = null; 11 | private Segment2D last = null; 12 | private boolean enclosed = false; 13 | 14 | public Polygon() 15 | { 16 | } 17 | 18 | public Polygon(Segment2D segment) 19 | { 20 | first = segment; 21 | if (first == null) 22 | return; 23 | last = first; 24 | for (Segment2D s = first.getNext(); s != null; s = s.getNext()) 25 | { 26 | if (s == first) 27 | { 28 | enclosed = true; 29 | break; 30 | } 31 | last = s; 32 | } 33 | } 34 | 35 | /** 36 | * Get the closest segment in this segment loop 37 | */ 38 | public Segment2D closestTo(Vector2 p) 39 | { 40 | Segment2D best = first; 41 | double bestDist = 99999; 42 | for (Segment2D s : this) 43 | { 44 | if (s.start.sub(p).vSize2() < bestDist) 45 | { 46 | bestDist = s.start.sub(p).vSize2(); 47 | best = s; 48 | } 49 | } 50 | return best; 51 | } 52 | 53 | public AABBrect getAABB() 54 | { 55 | AABBrect ret = new AABBrect(first); 56 | for (Segment2D s : this) 57 | { 58 | ret.addAABB(s); 59 | } 60 | return ret; 61 | } 62 | 63 | public void addEnd(Segment2D s) 64 | { 65 | if (enclosed) 66 | throw new RuntimeException(); 67 | if (first == null) 68 | { 69 | first = s; 70 | } 71 | if (last != null) 72 | last.setNext(s); 73 | last = s; 74 | } 75 | 76 | /** 77 | * removeEnd removes this segment from the segment list, and links up the next segment to the previous. Removing 1 point in the polygon. The point removed 78 | * is the endpoint of this segment. 79 | */ 80 | public void remove(Segment2D s) 81 | { 82 | if (s == first) 83 | { 84 | first = s.getNext(); 85 | //In case we are enclosed with a single segment, the next is the same one. So we are back to an empty polygon. 86 | if (first == s) 87 | first = null; 88 | } 89 | if (s == last) 90 | { 91 | last = last.getPrev(); 92 | //In case we are enclosed with a single segment, the prev is the same one. So we are back to an empty polygon. 93 | if (last == s) 94 | last = null; 95 | } 96 | 97 | if (s.getNext() == null) 98 | { 99 | if (enclosed) 100 | throw new RuntimeException(); 101 | // Remove 's' from the linked list. 102 | s.getPrev().setNext(null); 103 | } else 104 | { 105 | // Update the start point of s.next to the end of the previous point. Effectively removing 106 | // s.end from the polygon. 107 | s.getNext().update(s.getPrev().end, s.getNext().end); 108 | // Remove 's' from the linked list. 109 | // We can set 's.next' to null here, even if we are iterating over 's', 110 | // because the next point of iteration has already been stored by the iterator. 111 | Segment2D prev = s.getPrev(); 112 | Segment2D next = s.getNext(); 113 | prev.setNext(null); 114 | s.setNext(null); 115 | prev.setNext(next); 116 | } 117 | } 118 | 119 | public void close() 120 | { 121 | if (enclosed) 122 | throw new UnsupportedOperationException(); 123 | check(); 124 | enclosed = true; 125 | last.setNext(first); 126 | check(); 127 | } 128 | 129 | public Segment2D cutPoly(Segment2D s) 130 | { 131 | if (!enclosed) 132 | throw new UnsupportedOperationException(); 133 | enclosed = false; 134 | Segment2D ret = s.getPrev(); 135 | ret.setNext(null); 136 | return ret; 137 | } 138 | 139 | public void check() 140 | { 141 | if (first == null) 142 | return; 143 | if (enclosed) 144 | { 145 | if (first.getPrev() == null) 146 | throw new RuntimeException(); 147 | if (last.getNext() == null) 148 | throw new RuntimeException(); 149 | if (last.getNext() != first) 150 | throw new RuntimeException(); 151 | if (first.getPrev() != last) 152 | throw new RuntimeException(); 153 | for (Segment2D s = first.getNext(); s != first; s = s.getNext()) 154 | { 155 | if (s == null) 156 | throw new RuntimeException(); 157 | if (s.getPrev().getNext() != s) 158 | throw new RuntimeException(); 159 | } 160 | } else 161 | { 162 | if (first.getPrev() != null) 163 | throw new RuntimeException(); 164 | if (last.getNext() != null) 165 | throw new RuntimeException(); 166 | } 167 | } 168 | 169 | public boolean empty() 170 | { 171 | return first == null; 172 | } 173 | 174 | public Iterator iterator() 175 | { 176 | return new Segment2DIterator(); 177 | } 178 | 179 | private class Segment2DIterator implements Iterator 180 | { 181 | private Segment2D next; 182 | 183 | public Segment2DIterator() 184 | { 185 | this.next = first; 186 | } 187 | 188 | public boolean hasNext() 189 | { 190 | return next != null; 191 | } 192 | 193 | public Segment2D next() 194 | { 195 | Segment2D ret = next; 196 | next = next.getNext(); 197 | if (next == first) 198 | next = null; 199 | return ret; 200 | } 201 | 202 | public void remove() 203 | { 204 | throw new UnsupportedOperationException(); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/Segment2D.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import daid.sliceAndDaid.util.AABBrect; 4 | import daid.sliceAndDaid.util.Vector2; 5 | 6 | /** 7 | * Segment2D represents a line in 2D space. 8 | */ 9 | public class Segment2D extends AABBrect 10 | { 11 | public final static int TYPE_MODEL_SLICE = 0; 12 | public final static int TYPE_PERIMETER = 1; 13 | public final static int TYPE_MOVE = 2; 14 | public final static int TYPE_FILL = 3; 15 | public final static int TYPE_ERROR = 0xFFFF; 16 | 17 | public Vector2 start; 18 | public Vector2 end; 19 | private Vector2 normal; 20 | private Segment2D next, prev; 21 | 22 | public double lineWidth; 23 | public double feedRate; 24 | private int type; 25 | 26 | public Segment2D(int type, Vector2 start, Vector2 end) 27 | { 28 | // Make the AABB 1mm larger then the actual segment, to account for inaccuracies and moving 29 | // around the segment ends a bit. 30 | super(start, end, 1.0); 31 | 32 | this.type = type; 33 | this.lineWidth = -1; 34 | update(start, end); 35 | } 36 | 37 | public Segment2D(int type, Segment2D prev, Segment2D next) 38 | { 39 | super(prev.end, next.start, 1.0); 40 | this.type = type; 41 | this.start = prev.end; 42 | this.end = next.start; 43 | 44 | if (prev.next != null) 45 | prev.next.prev = null; 46 | prev.next = this; 47 | if (next.prev != null) 48 | next.prev.next = null; 49 | next.prev = this; 50 | 51 | this.prev = prev; 52 | this.next = next; 53 | 54 | update(this.start, this.end); 55 | } 56 | 57 | /** 58 | * For large updates we need to fix the normal, and the AABB. Only call this when the segment is 59 | * not in a Tree2D 60 | */ 61 | public void update(Vector2 start, Vector2 end) 62 | { 63 | this.start = start; 64 | this.end = end; 65 | this.normal = end.sub(start).crossZ().normal(); 66 | updateAABB(start, end, 1.0); 67 | } 68 | 69 | public String toString() 70 | { 71 | return "Segment:" + start + " " + end; 72 | } 73 | 74 | public Vector2 getIntersectionPoint(Segment2D other) 75 | { 76 | double x12 = start.x - end.x; 77 | double x34 = other.start.x - other.end.x; 78 | double y12 = start.y - end.y; 79 | double y34 = other.start.y - other.end.y; 80 | 81 | // Calculate the intersection of the 2 segments. 82 | double c = x12 * y34 - y12 * x34; 83 | if (Math.abs(c) < 0.0001) 84 | { 85 | return null; 86 | } else 87 | { 88 | double a = start.x * end.y - start.y * end.x; 89 | double b = other.start.x * other.end.y - other.start.y * other.end.x; 90 | 91 | return new Vector2((a * x34 - b * x12) / c, (a * y34 - b * y12) / c); 92 | } 93 | } 94 | 95 | public Vector2 getCollisionPoint(Segment2D other) 96 | { 97 | Vector2 p = getIntersectionPoint(other); 98 | if (p == null) 99 | return null; 100 | if ((p.x >= start.x && p.x <= end.x) || (p.x >= end.x && p.x <= start.x)) 101 | { 102 | if ((p.y >= start.y && p.y <= end.y) || (p.y >= end.y && p.y <= start.y)) 103 | return p; 104 | } 105 | return null; 106 | } 107 | 108 | public Vector2 getNormal() 109 | { 110 | return normal; 111 | } 112 | 113 | public int getType() 114 | { 115 | return type; 116 | } 117 | 118 | public void setType(int type) 119 | { 120 | this.type = type; 121 | } 122 | 123 | public void setNext(Segment2D newNext) 124 | { 125 | if (newNext == null) 126 | { 127 | if (next == null) 128 | throw new UnsupportedOperationException(); 129 | next.prev = null; 130 | next = null; 131 | } else 132 | { 133 | if (next != null) 134 | throw new UnsupportedOperationException(); 135 | if (newNext.prev != null) 136 | throw new UnsupportedOperationException(); 137 | next = newNext; 138 | next.prev = this; 139 | } 140 | } 141 | 142 | public Segment2D getNext() 143 | { 144 | return next; 145 | } 146 | 147 | public Segment2D getPrev() 148 | { 149 | return prev; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/SliceAndDaidMain.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.util.Vector; 6 | 7 | import javax.swing.SwingUtilities; 8 | 9 | import daid.sliceAndDaid.config.CraftConfig; 10 | import daid.sliceAndDaid.config.CraftConfigLoader; 11 | import daid.sliceAndDaid.tool.GCodeTool; 12 | import daid.sliceAndDaid.tool.PerimeterTool; 13 | import daid.sliceAndDaid.tool.PathTool; 14 | import daid.sliceAndDaid.tool.SliceTool; 15 | import daid.sliceAndDaid.tool.SpeedTool; 16 | import daid.sliceAndDaid.ui.ConfigWindow; 17 | import daid.sliceAndDaid.ui.PreviewFrame; 18 | import daid.sliceAndDaid.util.GCodeFile; 19 | import daid.sliceAndDaid.util.Logger; 20 | import daid.sliceAndDaid.util.Vector2; 21 | 22 | public class SliceAndDaidMain 23 | { 24 | public static void main(String[] args) 25 | { 26 | CraftConfigLoader.loadConfig(null); 27 | 28 | if (args.length < 1) 29 | { 30 | 31 | SwingUtilities.invokeLater(new Runnable() 32 | { 33 | public void run() 34 | { 35 | try 36 | { 37 | new ConfigWindow(); 38 | } catch (Exception e) 39 | { 40 | // We sometimes get a "Cannot write XdndAware property" exception in Java 41 | // 1.6.0_22 in Linux. Seems to be a java bug related to the text areas. 42 | 43 | // Just retry and hope for the best. 44 | if (e.getMessage().equals("Cannot write XdndAware property")) 45 | { 46 | new ConfigWindow(); 47 | return; 48 | } 49 | e.printStackTrace(); 50 | System.exit(-1); 51 | } 52 | } 53 | }); 54 | } else 55 | { 56 | for (int i = 0; i < args.length; i++) 57 | sliceModel(args[i]); 58 | } 59 | } 60 | 61 | public static void sliceModel(String filename) 62 | { 63 | long startTime = System.currentTimeMillis(); 64 | CraftConfig.lastSlicedFile = filename; 65 | CraftConfigLoader.saveConfig(null); 66 | 67 | Model m; 68 | try 69 | { 70 | m = new Model(filename); 71 | } catch (IOException e) 72 | { 73 | e.printStackTrace(); 74 | Logger.error("Failed to load model"); 75 | return; 76 | } 77 | m.center(); 78 | SliceTool slicer = new SliceTool(m); 79 | final Vector layers = slicer.sliceModel(CraftConfig.startLayerNr, CraftConfig.endLayerNr, 0.0); 80 | Logger.updateStatus("Creating skirt"); 81 | if (CraftConfig.skirtDistance > 0) 82 | { 83 | layers.get(0).skirt = new PerimeterTool(layers.get(0).modelPart, -CraftConfig.skirtDistance).createPerimeter().makeConvex(); 84 | } 85 | Logger.updateStatus("Creating outlines"); 86 | for (int i = 0; i < layers.size(); i++) 87 | { 88 | Logger.setProgress(i, layers.size()); 89 | LayerPart prevPart = layers.get(i).modelPart; 90 | for (int c = 0; c < CraftConfig.perimeterCount; c++) 91 | { 92 | if (c == 0) 93 | prevPart = new PerimeterTool(prevPart, CraftConfig.perimeterWidth * 0.5).createPerimeter(); 94 | else 95 | prevPart = new PerimeterTool(prevPart, CraftConfig.perimeterWidth).createPerimeter(); 96 | layers.get(i).outlinePart[c] = prevPart; 97 | } 98 | } 99 | Logger.updateStatus("Generating paths"); 100 | Vector2 startPoint = new Vector2(0, 0); 101 | for (int i = 0; i < layers.size(); i++) 102 | { 103 | Logger.setProgress(i, layers.size()); 104 | new PathTool(layers.get(i)).generatePath(startPoint); 105 | if (layers.get(i).pathStart != null) 106 | startPoint = layers.get(i).pathStart.start; 107 | } 108 | Logger.updateStatus("Setting speeds"); 109 | for (int i = 0; i < layers.size(); i++) 110 | { 111 | Logger.setProgress(i, layers.size()); 112 | new SpeedTool(layers.get(i)).updateSpeed(); 113 | } 114 | Logger.updateStatus("Generating GCode"); 115 | try 116 | { 117 | GCodeFile gcodeFile = new GCodeFile(new FileWriter(filename + "_export.gcode")); 118 | gcodeFile.writeComment("GCode generated by SliceAndDaid:" + CraftConfig.VERSION); 119 | 120 | for (String line : CraftConfig.startGCode.split("\n")) 121 | gcodeFile.write(line); 122 | // gcodeFile.write("M101; extruder on (to get skeinlayer working)"); 123 | 124 | for (int i = 0; i < layers.size(); i++) 125 | { 126 | Logger.setProgress(i, layers.size()); 127 | new GCodeTool(layers.get(i), gcodeFile).generateGCode(); 128 | } 129 | 130 | // gcodeFile.write("M103; extruder off (to get skeinlayer working)"); 131 | for (String line : CraftConfig.endGCode.split("\n")) 132 | gcodeFile.write(line); 133 | gcodeFile.close(); 134 | Logger.message("Expected print time: " + ((int) gcodeFile.getBuildTime() / 60) + " minutes"); 135 | } catch (IOException e) 136 | { 137 | e.printStackTrace(); 138 | } 139 | 140 | /* Post slicing */ 141 | long sliceTime = System.currentTimeMillis() - startTime; 142 | Logger.message("Slice time: " + (((double) sliceTime) / 1000) + " seconds"); 143 | SwingUtilities.invokeLater(new Runnable() 144 | { 145 | public void run() 146 | { 147 | new PreviewFrame(layers); 148 | } 149 | }); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/config/CraftConfig.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.config; 2 | 3 | /** 4 | * The CraftConfig class contains the configurable 5 | * settings for the slicer. Reflection and annotations 6 | * are used to make it easy to generate the configuration 7 | * dialog. 8 | * NOTE: Do not auto format this file. Manual format keeps it readable! 9 | */ 10 | public class CraftConfig 11 | { 12 | public static final String VERSION = "Dev-Prerelease"; 13 | 14 | public static final int GCODE_FULL = 0; 15 | public static final int GCODE_COMPACT = 1; 16 | public static final int GCODE_TINY_COMPACT = 2; 17 | 18 | @Setting(level = Setting.LEVEL_STARTER, group = "Layers", 19 | title = "Layer height (mm)", 20 | description = "Height of each sliced layer", 21 | minValue = 0.0, maxValue = 1.0) 22 | public static double layerHeight = 0.2; 23 | 24 | @Setting(level = Setting.LEVEL_STARTER, group = "Perimeter", 25 | title = "Width of the perimeter lines", 26 | description = "The width of the perimeter lines, a good value is the inner radius of your nozzle tip.", 27 | minValue = 0, maxValue = Integer.MAX_VALUE) 28 | public static double perimeterWidth = 0.4; 29 | 30 | @Setting(level = Setting.LEVEL_NORMAL, group = "Perimeter", 31 | title = "Perimeter line count", 32 | description = "Amount of perimeter walls", 33 | minValue = 1, maxValue = Integer.MAX_VALUE) 34 | public static int perimeterCount = 3; 35 | 36 | @Setting(level = Setting.LEVEL_STARTER, group = "Speed", 37 | title = "Print speed (mm/s)", 38 | description = "Speed at which the head is moved while it's printing", 39 | minValue = 0, maxValue = 10000) 40 | public static double printSpeed = 40.0; 41 | 42 | @Setting(level = Setting.LEVEL_NORMAL, group = "Speed", 43 | title = "First layer print speed (mm/s)", 44 | description = "Speed at which the head is moved while it's printing the first layer", 45 | minValue = 0, maxValue = 10000) 46 | public static double layerZeroPrintSpeed = 20.0; 47 | 48 | @Setting(level = Setting.LEVEL_NORMAL, group = "Speed", 49 | title = "Max speedup per layer (mm/s)", 50 | description = "Amount of speed increase per layer after the first layer, until it reaches the print speed.", 51 | minValue = 0, maxValue = 10000) 52 | public static double layerPrintSpeedIncrease = 10.0; 53 | 54 | @Setting(level = Setting.LEVEL_NORMAL, group = "Speed", 55 | title = "Travel speed (mm/s)", 56 | description = "Speed at which the head is moved while it's not printing", 57 | minValue = 0, maxValue = 10000) 58 | public static double travelSpeed = 150.0; 59 | 60 | @Setting(level = Setting.LEVEL_STARTER, group = "Dimensions", 61 | title = "Filament diameter (mm)", 62 | description = "The diameter of the filament, as accurate as possible.\n"+ 63 | "If you cannot measure it accurate then manually tweak it.\n"+ 64 | "If you get to little extrusion reduce this number, if you get to much, increase this number.", 65 | minValue = 0, maxValue = 10) 66 | public static double filamentDiameter = 2.89; 67 | 68 | @Setting(level = Setting.LEVEL_ADVANCED, group = "Speed", 69 | title = "Minimum layer time (s)", 70 | description = "The minimal amount of time spend to print a single layer.\n" + 71 | "Gives time to cool the layer before the next one is printed.", 72 | minValue = 0, maxValue = 200) 73 | public static double minLayerTime = 10; 74 | 75 | @Setting(level = Setting.LEVEL_NORMAL, group = "Skirt", 76 | title = "Skirt distance (mm)", 77 | description = "Distance of the skirt (outline around layer 0) from the model. Use 0 to disable.", 78 | minValue = 0, maxValue = 10) 79 | public static double skirtDistance = 6.0; 80 | 81 | @Setting(level = Setting.LEVEL_KITCHENSINK, group = "Layers", 82 | title = "First layer slice height (%)", 83 | description = "Starting height of the first slice in the model. 50% is the default.", 84 | minValue = 0, maxValue = 200) 85 | public static int firstLayerHeightPercent = 50; 86 | 87 | @Setting(level = Setting.LEVEL_ADVANCED, group = "Perimeter", 88 | title = "Minimal line segment cosinus value", 89 | description = "If the cosinus of the line angle difference is higher then this value then 2 lines are joined into 1.\nSpeeding up the slicing, and creating less gcode commands. Lower values makes circles less round,\nfor a faster slicing and less GCode. A value of 1.0 leaves every line intact.", 90 | minValue = 0.95, maxValue = 1.0) 91 | public static double joinMinCosAngle = 0.995; 92 | 93 | @Setting(level = Setting.LEVEL_KITCHENSINK, group = "Layers", 94 | title = "Start layer number", 95 | description = "First layer that is sliced, can be used to remove the bottom X layers", 96 | minValue = 0, maxValue = Integer.MAX_VALUE) 97 | public static int startLayerNr = 0; 98 | 99 | @Setting(level = Setting.LEVEL_KITCHENSINK, group = "Layers", 100 | title = "Final layer number", 101 | description = "Last layer that is sliced, can be used to remove the top X layers.", 102 | minValue = 0, maxValue = Integer.MAX_VALUE) 103 | public static int endLayerNr = Integer.MAX_VALUE; 104 | 105 | @Setting(level = Setting.LEVEL_KITCHENSINK, group = "Perimeter", 106 | title = "Cap perimeter corners", 107 | description = "Cap off tight corners in the perimeter.") 108 | public static boolean perimeterCap = true; 109 | @Setting(level = Setting.LEVEL_KITCHENSINK, 110 | title = "Minimum segment length (mm)", 111 | description = "Remove segments shorter then this length.") 112 | public static double minSegmentLength = 0.1; 113 | 114 | @Setting(level = Setting.LEVEL_ADVANCED, group = "GCode", 115 | title = "GCode format", 116 | description = "Different GCode exports types are supported.\n"+ 117 | "Full: exports everything with comments. Use this for debugging, or post processing of the GCode.\n" + 118 | "Compact: removes the comments, and assumes the last feedrate will be reused. (About 25% smaller then full)\n" + 119 | "Tiny compact: tries to export the minimum amount of GCode required. Not all firmwares and parsers will work with this (About 10% smaller then compact).", 120 | enumName = "GCODE") 121 | public static int gcodeType = GCODE_COMPACT; 122 | 123 | @Setting(level = Setting.LEVEL_HIDDEN) 124 | public static String startGCode = "M98 E926; Set the number of steps per E mm\n" + 125 | "G28; Move to origin\n" + 126 | "G92 X-105 Y-105 Z0; Put the 'origin' on the center of the platform\n" + 127 | "G1 Z5 F180; Move the head up a bit\n" + 128 | "G1 X0 Y0; Move to the center of the platfrom\n"+ 129 | "M106 S255; Turn on the fan\n" + 130 | "G1 Z0 F180; Move the head down for printing of layer 0"; 131 | 132 | @Setting(level = Setting.LEVEL_HIDDEN) 133 | public static String endGCode = "G1 X-200 Y-200; Move the X/Y away from the printed object\n" + 134 | "M104 S0; Turn off the extruder temperature"; 135 | 136 | @Setting(level = Setting.LEVEL_HIDDEN, 137 | minValue = Setting.LEVEL_STARTER, maxValue = Setting.LEVEL_KITCHENSINK) 138 | public static int showLevel = Setting.LEVEL_STARTER; 139 | @Setting(level = Setting.LEVEL_HIDDEN) 140 | public static String lastSlicedFile = ""; 141 | } 142 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/config/CraftConfigLoader.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.config; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.lang.reflect.Field; 10 | 11 | import daid.sliceAndDaid.util.Logger; 12 | 13 | public class CraftConfigLoader 14 | { 15 | /*************************** 16 | * Load and save functions 17 | ***************************/ 18 | 19 | /** 20 | * loadConfig 21 | * 22 | * Loads the configuration from a file, use 'null' for the default config file. 23 | */ 24 | public static void loadConfig(String filename) 25 | { 26 | if (filename == null) 27 | filename = System.getProperty("user.home") + "/.SliceAndDaid.conf"; 28 | BufferedReader br = null; 29 | try 30 | { 31 | br = new BufferedReader(new FileReader(filename)); 32 | } catch (FileNotFoundException e) 33 | { 34 | return; 35 | } 36 | String line; 37 | String section = null; 38 | try 39 | { 40 | while ((line = br.readLine()) != null) 41 | { 42 | if (line.startsWith(";")) 43 | continue; 44 | if (line.startsWith("[") && line.endsWith("]")) 45 | { 46 | section = line; 47 | continue; 48 | } 49 | if (line.indexOf('=') < 0) 50 | continue; 51 | String key = line.substring(0, line.indexOf('=')); 52 | String value = line.substring(line.indexOf('=') + 1); 53 | if ("[SliceAndDaid config]".equals(section)) 54 | { 55 | setField(key, value); 56 | } 57 | } 58 | } catch (IOException e) 59 | { 60 | Logger.error("IOException during loading of config file..."); 61 | } 62 | } 63 | 64 | private static void setField(String key, String value) 65 | { 66 | Class c = CraftConfig.class; 67 | Field f = null; 68 | try 69 | { 70 | f = c.getField(key); 71 | if (f == null) 72 | return; 73 | Setting s = f.getAnnotation(Setting.class); 74 | if (f.getType() == Double.TYPE) 75 | { 76 | double v = Double.parseDouble(value); 77 | if (s != null && v < s.minValue()) 78 | v = s.minValue(); 79 | if (s != null && v > s.maxValue()) 80 | v = s.maxValue(); 81 | f.setDouble(null, v); 82 | } else if (f.getType() == Integer.TYPE) 83 | { 84 | int v = Integer.parseInt(value); 85 | if (s != null && v < s.minValue()) 86 | v = (int) s.minValue(); 87 | if (s != null && v > s.maxValue()) 88 | v = (int) s.maxValue(); 89 | f.setInt(null, v); 90 | } else if (f.getType() == Boolean.TYPE) 91 | { 92 | f.setBoolean(null, Boolean.parseBoolean(value)); 93 | } else if (f.getType() == String.class) 94 | { 95 | f.set(null, value.toString().replace("\\n", "\n")); 96 | } else 97 | { 98 | throw new RuntimeException("Unknown config type: " + f.getType()); 99 | } 100 | } catch (IllegalArgumentException e) 101 | { 102 | e.printStackTrace(); 103 | } catch (IllegalAccessException e) 104 | { 105 | e.printStackTrace(); 106 | } catch (SecurityException e) 107 | { 108 | e.printStackTrace(); 109 | } catch (NoSuchFieldException e) 110 | { 111 | Logger.warning("Found: " + key + " in the configuration, but I don't know this setting"); 112 | } 113 | 114 | } 115 | 116 | /** 117 | * saveConfig 118 | * 119 | * Saves the configuration to a file, use 'null' for the default config file. 120 | */ 121 | public static void saveConfig(String filename) 122 | { 123 | if (filename == null) 124 | filename = System.getProperty("user.home") + "/.SliceAndDaid.conf"; 125 | try 126 | { 127 | BufferedWriter bw = new BufferedWriter(new FileWriter(filename)); 128 | bw.write(";Saved with version: " + CraftConfig.VERSION + "\n"); 129 | bw.write("[SliceAndDaid config]\n"); 130 | Class configClass = CraftConfig.class; 131 | for (final Field f : configClass.getFields()) 132 | { 133 | Setting s = f.getAnnotation(Setting.class); 134 | if (s == null) 135 | continue; 136 | try 137 | { 138 | bw.write(f.getName() + "=" + f.get(null).toString().replace("\n", "\\n") + "\n"); 139 | } catch (IllegalArgumentException e) 140 | { 141 | e.printStackTrace(); 142 | } catch (IllegalAccessException e) 143 | { 144 | e.printStackTrace(); 145 | } 146 | } 147 | bw.close(); 148 | } catch (IOException e1) 149 | { 150 | // TODO Auto-generated catch block 151 | e1.printStackTrace(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/config/Setting.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Setting 11 | { 12 | int LEVEL_STARTER = 0; 13 | int LEVEL_NORMAL = 1; 14 | int LEVEL_ADVANCED = 2; 15 | int LEVEL_KITCHENSINK = 3; 16 | int LEVEL_HIDDEN = 4; 17 | 18 | public String title() default ""; 19 | 20 | public String description() default ""; 21 | 22 | public double minValue() default Double.MIN_VALUE; 23 | 24 | public double maxValue() default Double.MAX_VALUE; 25 | 26 | public int level() default Setting.LEVEL_NORMAL; 27 | 28 | public String enumName() default ""; 29 | 30 | public String group() default ""; 31 | } 32 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/tool/GCodeTool.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.tool; 2 | 3 | import java.io.IOException; 4 | 5 | import daid.sliceAndDaid.Layer; 6 | import daid.sliceAndDaid.Segment2D; 7 | import daid.sliceAndDaid.config.CraftConfig; 8 | import daid.sliceAndDaid.util.GCodeFile; 9 | 10 | public class GCodeTool 11 | { 12 | private Layer layer; 13 | private GCodeFile file; 14 | 15 | public GCodeTool(Layer layer, GCodeFile file) 16 | { 17 | this.layer = layer; 18 | this.file = file; 19 | } 20 | 21 | public void generateGCode() throws IOException 22 | { 23 | double filamentMM3PerMM = Math.PI * (CraftConfig.filamentDiameter / 2) * (CraftConfig.filamentDiameter / 2); 24 | 25 | file.writeComment("LAYER:" + layer.layerNr); 26 | file.writeMoveZ((double) (layer.layerNr + 1) * CraftConfig.layerHeight, CraftConfig.travelSpeed, "Move to layer: " + layer.layerNr); 27 | if (layer.pathStart == null) 28 | return; 29 | file.writeMoveXY(layer.pathStart.start.x, layer.pathStart.start.y, CraftConfig.travelSpeed, ""); 30 | for (Segment2D s = layer.pathStart; s != null; s = s.getNext()) 31 | { 32 | if (s.lineWidth < 0) 33 | { 34 | file.writeMoveXY(s.end.x, s.end.y, s.feedRate, ""); 35 | } else 36 | { 37 | // First calculate the amount of filament we need in mm3 38 | double filamentAmount = s.end.sub(s.start).vSize() * s.lineWidth * CraftConfig.layerHeight; 39 | // Then divide this by the amount of mm3 we have per mm filament, so we get the 40 | // amount of mm of filament we need to extrude. 41 | filamentAmount = filamentAmount / filamentMM3PerMM; 42 | file.writeMoveXYE(s.end.x, s.end.y, filamentAmount, s.feedRate, ""); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/tool/PathTool.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.tool; 2 | 3 | import java.util.Vector; 4 | 5 | import daid.sliceAndDaid.Layer; 6 | import daid.sliceAndDaid.Polygon; 7 | import daid.sliceAndDaid.Segment2D; 8 | import daid.sliceAndDaid.util.Vector2; 9 | 10 | /** 11 | * The path tool is the final tool run before the GCode is generated. 12 | * 13 | * It takes all the separate lines and links those into a single large path, ready for GCode generation. 14 | */ 15 | public class PathTool 16 | { 17 | private Layer layer; 18 | 19 | public PathTool(Layer layer) 20 | { 21 | this.layer = layer; 22 | } 23 | 24 | public void generatePath(Vector2 bestStartPoint) 25 | { 26 | Segment2D prev = null; 27 | if (layer.skirt != null) 28 | { 29 | Polygon poly = layer.skirt.getLargestPolygon(); 30 | if (poly != null) 31 | { 32 | prev = poly.closestTo(bestStartPoint); 33 | layer.pathStart = prev; 34 | prev = poly.cutPoly(prev); 35 | } 36 | } 37 | 38 | for (int i = 0; i < layer.outlinePart.length; i++) 39 | { 40 | // Find the largest polygon. So we start with the biggest outline first. 41 | Polygon nextPoly = layer.outlinePart[i].getLargestPolygon(); 42 | if (nextPoly == null) 43 | return; 44 | 45 | Vector polys = layer.outlinePart[i].getPolygonListClone(); 46 | polys.remove(nextPoly); 47 | polys.add(0, nextPoly); 48 | 49 | while (polys.size() > 0) 50 | { 51 | nextPoly = polys.get(0); 52 | if (prev != null) 53 | { 54 | for (int n = 0; n < polys.size(); n++) 55 | { 56 | if (polys.get(n).getAABB().getAABBDist(prev) < nextPoly.getAABB().getAABBDist(prev)) 57 | nextPoly = polys.get(n); 58 | } 59 | } 60 | polys.remove(nextPoly); 61 | if (prev == null) 62 | { 63 | Segment2D startSegment = nextPoly.closestTo(bestStartPoint); 64 | layer.pathStart = startSegment; 65 | prev = nextPoly.cutPoly(startSegment); 66 | } else 67 | { 68 | Segment2D startSegment = nextPoly.closestTo(prev.end); 69 | Segment2D newPrev = nextPoly.cutPoly(startSegment); 70 | new Segment2D(Segment2D.TYPE_MOVE, prev, startSegment); 71 | prev = newPrev; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/tool/PerimeterTool.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.tool; 2 | 3 | import sun.reflect.generics.tree.Tree; 4 | import daid.sliceAndDaid.LayerPart; 5 | import daid.sliceAndDaid.Polygon; 6 | import daid.sliceAndDaid.Segment2D; 7 | import daid.sliceAndDaid.config.CraftConfig; 8 | import daid.sliceAndDaid.util.AABBTree; 9 | import daid.sliceAndDaid.util.Vector2; 10 | 11 | /** 12 | * The perimeter is the outer lines of the object, the "walls" so to say. 13 | */ 14 | public class PerimeterTool 15 | { 16 | private LayerPart layerPart; 17 | private double distance; 18 | 19 | public PerimeterTool(LayerPart layerPart, double distance) 20 | { 21 | this.layerPart = layerPart; 22 | this.distance = distance; 23 | } 24 | 25 | public LayerPart createPerimeter() 26 | { 27 | LayerPart ret = new LayerPart(layerPart); 28 | AABBTree tree = new AABBTree(); 29 | for (Polygon poly : layerPart) 30 | { 31 | Polygon newPoly = new Polygon(); 32 | Segment2D first = null, prev = null; 33 | for (Segment2D s : poly) 34 | { 35 | Vector2 start = s.start.sub(s.getNormal().mul(distance)); 36 | Vector2 end = s.end.sub(s.getNormal().mul(distance)); 37 | Segment2D newSeg = new Segment2D(Segment2D.TYPE_PERIMETER, start, end); 38 | newSeg.lineWidth = CraftConfig.perimeterWidth; 39 | 40 | newPoly.addEnd(newSeg); 41 | if (prev == null) 42 | { 43 | first = newSeg; 44 | } else 45 | { 46 | linkUp(ret, prev, newSeg); 47 | } 48 | 49 | prev = newSeg; 50 | } 51 | 52 | if (!newPoly.empty()) 53 | { 54 | newPoly.close(); 55 | linkUp(ret, prev, first); 56 | for (Segment2D s : newPoly) 57 | { 58 | tree.insert(s); 59 | } 60 | ret.addPolygon(newPoly); 61 | } 62 | } 63 | 64 | return ret; 65 | } 66 | 67 | /** 68 | * Link up the 2 segments to each other, this will extend the segment so that the 2 segments cross, unless the extend it longer then the 'distance', at 69 | * which point an extra segment is created. This will help with very high angle corners. 70 | */ 71 | private void linkUp(LayerPart ret, Segment2D prev, Segment2D next) 72 | { 73 | Vector2 p = prev.getIntersectionPoint(next); 74 | if (p == null) 75 | p = prev.end.add(next.start).div(2); 76 | // If the intersection point between the 2 moved lines is a bit further away then the line 77 | // distance, then we are a tight corner and we need to be capped. 78 | if (CraftConfig.perimeterCap && prev.end.sub(p).vSize2() > distance * 1.1 * distance * 1.1) 79 | { 80 | Vector2 p1 = prev.end.add(p.sub(prev.end).normal().mul(distance)); 81 | Vector2 p2 = next.start.add(p.sub(next.start).normal().mul(distance)); 82 | 83 | prev.end = p1; 84 | next.start = p2; 85 | Segment2D newSeg = new Segment2D(Segment2D.TYPE_PERIMETER, p1, p2); 86 | newSeg.lineWidth = CraftConfig.perimeterWidth; 87 | prev.setNext(null); 88 | prev.setNext(newSeg); 89 | newSeg.setNext(next); 90 | } else 91 | { 92 | prev.end = p; 93 | next.start = p; 94 | 95 | //prev.setNext(next); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/tool/SliceTool.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.tool; 2 | 3 | import java.util.Vector; 4 | 5 | import daid.sliceAndDaid.Layer; 6 | import daid.sliceAndDaid.Model; 7 | import daid.sliceAndDaid.Segment2D; 8 | import daid.sliceAndDaid.config.CraftConfig; 9 | import daid.sliceAndDaid.util.Logger; 10 | import daid.sliceAndDaid.util.Triangle; 11 | import daid.sliceAndDaid.util.Vector3; 12 | 13 | /** 14 | * The slice tool slices the model into layers, it does so by going trough all model triangles and 15 | * slice those into 2D lines. 16 | */ 17 | public class SliceTool 18 | { 19 | private Model model; 20 | 21 | public SliceTool(Model model) 22 | { 23 | this.model = model; 24 | } 25 | 26 | public Vector sliceModel(int startLayer, int endLayer, double extraLayerOffset) 27 | { 28 | Vector layers = new Vector(); 29 | 30 | double layerHeight = CraftConfig.layerHeight; 31 | Vector3 modelMin = model.getMin(); 32 | Vector3 modelMax = model.getMax(); 33 | double firstLayerHeight = ((double) CraftConfig.firstLayerHeightPercent) / 100.0 + extraLayerOffset; 34 | int layerCount = (int) (modelMax.z / layerHeight + firstLayerHeight); 35 | 36 | int firstLayer = startLayer; 37 | int lastLayer = endLayer; 38 | if (lastLayer > layerCount) 39 | lastLayer = layerCount; 40 | Logger.updateStatus("Slicing layers"); 41 | Logger.message("Slicing " + (lastLayer - firstLayer) + " layers"); 42 | for (int i = firstLayer; i < lastLayer; i++) 43 | { 44 | layers.add(new Layer(i, modelMin.x, modelMin.y, modelMax.x, modelMax.y)); 45 | } 46 | int n = 0; 47 | for (Triangle t : model.triangles) 48 | { 49 | Logger.setProgress(n++, model.triangles.size()); 50 | 51 | double zMin = t.point[0].z; 52 | double zMax = t.point[0].z; 53 | if (t.point[1].z < zMin) 54 | zMin = t.point[1].z; 55 | if (t.point[2].z < zMin) 56 | zMin = t.point[2].z; 57 | if (t.point[1].z > zMax) 58 | zMax = t.point[1].z; 59 | if (t.point[2].z > zMax) 60 | zMax = t.point[2].z; 61 | for (int i = (int) (zMin / layerHeight + firstLayerHeight); i <= (int) (zMax / layerHeight + firstLayerHeight); i++) 62 | { 63 | if (i >= firstLayer && i < lastLayer) 64 | { 65 | double layerZ = (((double) i) + firstLayerHeight) * layerHeight; 66 | Segment2D s = t.project2D(layerZ); 67 | if (s != null) 68 | layers.get(i - firstLayer).addModelSegment(s); 69 | } 70 | } 71 | } 72 | 73 | Logger.updateStatus("Optimizing layers"); 74 | for (int i = 0; i < layers.size(); i++) 75 | { 76 | Logger.setProgress(i, layers.size()); 77 | layers.get(i).optimize(); 78 | } 79 | return layers; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/tool/SpeedTool.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.tool; 2 | 3 | import daid.sliceAndDaid.Layer; 4 | import daid.sliceAndDaid.Segment2D; 5 | import daid.sliceAndDaid.config.CraftConfig; 6 | 7 | public class SpeedTool 8 | { 9 | private Layer layer; 10 | 11 | public SpeedTool(Layer layer) 12 | { 13 | this.layer = layer; 14 | } 15 | 16 | public void updateSpeed() 17 | { 18 | double layerTime = 0; 19 | for (Segment2D s = layer.pathStart; s != null; s = s.getNext()) 20 | { 21 | if (s.lineWidth < 0) 22 | { 23 | s.feedRate = CraftConfig.travelSpeed; 24 | } else 25 | { 26 | s.feedRate = CraftConfig.layerZeroPrintSpeed + CraftConfig.layerPrintSpeedIncrease * layer.layerNr; 27 | if (s.feedRate > CraftConfig.printSpeed) 28 | s.feedRate = CraftConfig.printSpeed; 29 | } 30 | layerTime += s.start.sub(s.end).vSize() / s.feedRate; 31 | } 32 | 33 | if (layerTime < CraftConfig.minLayerTime) 34 | { 35 | double multiply = layerTime / CraftConfig.minLayerTime; 36 | for (Segment2D s = layer.pathStart; s != null; s = s.getNext()) 37 | { 38 | s.feedRate *= multiply; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/ui/ConfigWindow.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.ui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Component; 6 | import java.awt.Dimension; 7 | import java.awt.Font; 8 | import java.awt.GridBagConstraints; 9 | import java.awt.GridBagLayout; 10 | import java.awt.Insets; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | import java.awt.event.FocusAdapter; 14 | import java.awt.event.FocusEvent; 15 | import java.io.File; 16 | import java.lang.reflect.Field; 17 | import java.util.HashSet; 18 | import java.util.Vector; 19 | 20 | import javax.swing.BorderFactory; 21 | import javax.swing.BoxLayout; 22 | import javax.swing.DefaultListCellRenderer; 23 | import javax.swing.JButton; 24 | import javax.swing.JCheckBox; 25 | import javax.swing.JComboBox; 26 | import javax.swing.JFileChooser; 27 | import javax.swing.JFrame; 28 | import javax.swing.JLabel; 29 | import javax.swing.JList; 30 | import javax.swing.JOptionPane; 31 | import javax.swing.JPanel; 32 | import javax.swing.JScrollPane; 33 | import javax.swing.JSpinner; 34 | import javax.swing.JTabbedPane; 35 | import javax.swing.JTextArea; 36 | import javax.swing.SpinnerNumberModel; 37 | import javax.swing.event.ChangeEvent; 38 | import javax.swing.event.ChangeListener; 39 | import javax.swing.filechooser.FileFilter; 40 | 41 | import daid.sliceAndDaid.SliceAndDaidMain; 42 | import daid.sliceAndDaid.config.CraftConfig; 43 | import daid.sliceAndDaid.config.CraftConfigLoader; 44 | import daid.sliceAndDaid.config.Setting; 45 | import daid.sliceAndDaid.util.Logger; 46 | 47 | /** 48 | * The ConfigWindow class generates a JFrame window with the configurable options. 49 | * 50 | * It uses reflection to get the configurable settings. This makes adding new settings easy. 51 | * 52 | * NOTE: I suck at UI coding. 53 | */ 54 | public class ConfigWindow extends JFrame 55 | { 56 | private static final long serialVersionUID = 1L; 57 | 58 | private JPanel configSettingsPanel; 59 | private JPanel actionPanel; 60 | 61 | public ConfigWindow() 62 | { 63 | this.setTitle("SliceAndDaid - " + CraftConfig.VERSION); 64 | this.setResizable(false); 65 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 66 | 67 | JTabbedPane tabbedPane = new JTabbedPane(); 68 | this.configSettingsPanel = new JPanel(); 69 | this.configSettingsPanel.setLayout(new BoxLayout(this.configSettingsPanel, BoxLayout.Y_AXIS)); 70 | this.actionPanel = new JPanel(new GridBagLayout()); 71 | 72 | final JTextArea startCodeTextArea = new JTextArea(CraftConfig.startGCode); 73 | final JTextArea endCodeTextArea = new JTextArea(CraftConfig.endGCode); 74 | startCodeTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); 75 | endCodeTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); 76 | 77 | startCodeTextArea.addFocusListener(new FocusAdapter() 78 | { 79 | public void focusLost(FocusEvent e) 80 | { 81 | CraftConfig.startGCode = startCodeTextArea.getText(); 82 | CraftConfigLoader.saveConfig(null); 83 | } 84 | }); 85 | endCodeTextArea.addFocusListener(new FocusAdapter() 86 | { 87 | public void focusLost(FocusEvent e) 88 | { 89 | CraftConfig.endGCode = endCodeTextArea.getText(); 90 | CraftConfigLoader.saveConfig(null); 91 | } 92 | }); 93 | 94 | tabbedPane.addTab("Settings", this.configSettingsPanel); 95 | tabbedPane.addTab("Start GCode", new JScrollPane(startCodeTextArea)); 96 | tabbedPane.addTab("End GCode", new JScrollPane(endCodeTextArea)); 97 | 98 | GridBagConstraints c = new GridBagConstraints(); 99 | c.gridy = 0; 100 | c.anchor = GridBagConstraints.WEST; 101 | c.insets = new Insets(1, 1, 1, 1); 102 | c.fill = GridBagConstraints.HORIZONTAL; 103 | 104 | JButton sliceButton = new JButton("Slice"); 105 | sliceButton.addActionListener(new ActionListener() 106 | { 107 | public void actionPerformed(ActionEvent e) 108 | { 109 | final JFileChooser fc = new JFileChooser(); 110 | fc.setFileFilter(new FileFilter() 111 | { 112 | public boolean accept(File f) 113 | { 114 | if (f.isDirectory()) 115 | return true; 116 | return f.getName().endsWith(".stl"); 117 | } 118 | 119 | public String getDescription() 120 | { 121 | return null; 122 | } 123 | 124 | }); 125 | fc.setSelectedFile(new File(CraftConfig.lastSlicedFile)); 126 | int returnVal = fc.showOpenDialog(null); 127 | if (returnVal == JFileChooser.APPROVE_OPTION) 128 | { 129 | final LogWindow logWindow = new LogWindow(); 130 | new Thread(new Runnable() 131 | { 132 | public void run() 133 | { 134 | try 135 | { 136 | SliceAndDaidMain.sliceModel(fc.getSelectedFile().toString()); 137 | logWindow.dispose(); 138 | } catch (Exception e) 139 | { 140 | e.printStackTrace(); 141 | logWindow.dispose(); 142 | StringBuilder sb = new StringBuilder(); 143 | sb.append(e.toString()); 144 | sb.append("\n"); 145 | for (StackTraceElement el : e.getStackTrace()) 146 | { 147 | sb.append(el.toString()); 148 | sb.append("\n"); 149 | } 150 | JOptionPane.showMessageDialog(null, sb, "Exception", JOptionPane.ERROR_MESSAGE); 151 | } 152 | } 153 | }).start(); 154 | ConfigWindow.this.dispose(); 155 | } 156 | } 157 | }); 158 | this.actionPanel.add(sliceButton, c); 159 | 160 | final JComboBox levelSelect = new JComboBox(new String[] { "Starter", "Normal", "Advance", "+Kitchen sink" }); 161 | levelSelect.addActionListener(new ActionListener() 162 | { 163 | public void actionPerformed(ActionEvent e) 164 | { 165 | CraftConfig.showLevel = levelSelect.getSelectedIndex(); 166 | Color buttonBgColor = null; 167 | for (Component c : levelSelect.getComponents()) 168 | { 169 | if (c instanceof JButton) 170 | buttonBgColor = ((JButton) c).getBackground(); 171 | } 172 | levelSelect.setBackground(levelColor(CraftConfig.showLevel)); 173 | for (Component c : levelSelect.getComponents()) 174 | { 175 | if (c instanceof JButton) 176 | ((JButton) c).setBackground(buttonBgColor); 177 | } 178 | CraftConfigLoader.saveConfig(null); 179 | createConfigFields(); 180 | } 181 | }); 182 | levelSelect.setSelectedIndex(CraftConfig.showLevel); 183 | levelSelect.setRenderer(new DefaultListCellRenderer() 184 | { 185 | private static final long serialVersionUID = 1L; 186 | 187 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) 188 | { 189 | super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 190 | if (isSelected) 191 | this.setBackground(levelColor(index).darker()); 192 | else 193 | this.setBackground(levelColor(index)); 194 | return this; 195 | } 196 | }); 197 | this.actionPanel.add(levelSelect, c); 198 | 199 | this.add(tabbedPane); 200 | this.add(actionPanel, BorderLayout.SOUTH); 201 | 202 | createConfigFields(); 203 | this.setVisible(true); 204 | } 205 | 206 | private void createConfigFields() 207 | { 208 | configSettingsPanel.removeAll(); 209 | 210 | HashSet doneGroups = new HashSet(); 211 | 212 | for (final Field f : CraftConfig.class.getFields()) 213 | { 214 | final Setting s = f.getAnnotation(Setting.class); 215 | if (s == null) 216 | continue; 217 | if (doneGroups.contains(s.group())) 218 | continue; 219 | doneGroups.add(s.group()); 220 | JPanel p = new JPanel(new GridBagLayout()); 221 | p.setBorder(BorderFactory.createTitledBorder(s.group())); 222 | if (addConfigFields(p, s.group()) > 0) 223 | configSettingsPanel.add(p); 224 | } 225 | 226 | this.pack(); 227 | this.setLocationRelativeTo(null); 228 | } 229 | 230 | private int addConfigFields(JPanel p, String groupName) 231 | { 232 | GridBagConstraints c = new GridBagConstraints(); 233 | c.gridy = 0; 234 | c.anchor = GridBagConstraints.WEST; 235 | c.insets = new Insets(1, 1, 1, 1); 236 | c.fill = GridBagConstraints.HORIZONTAL; 237 | c.weighty = 1; 238 | for (final Field f : CraftConfig.class.getFields()) 239 | { 240 | final Setting s = f.getAnnotation(Setting.class); 241 | Object obj = null; 242 | 243 | try 244 | { 245 | obj = f.get(null).toString(); 246 | 247 | if (s == null || obj == null) 248 | continue; 249 | if (!s.group().equals(groupName)) 250 | continue; 251 | if (s.level() > CraftConfig.showLevel) 252 | continue; 253 | final Component comp = getSwingComponentForField(f, s); 254 | 255 | if (comp == null) 256 | continue; 257 | 258 | final JLabel label = new JLabel(s.title() + ":"); 259 | JButton helpButton = null; 260 | 261 | if (!s.description().equals("")) 262 | { 263 | helpButton = new JButton("?"); 264 | helpButton.setMargin(new java.awt.Insets(0, 1, 0, 1)); 265 | helpButton.addActionListener(new ActionListener() 266 | { 267 | public void actionPerformed(ActionEvent e) 268 | { 269 | JOptionPane.showMessageDialog(label, s.description()); 270 | } 271 | }); 272 | helpButton.setBackground(levelColor(s.level())); 273 | } 274 | 275 | comp.setPreferredSize(new Dimension(100, 25)); 276 | c.weightx = 0; 277 | c.gridx = 0; 278 | p.add(helpButton, c); 279 | c.weightx = 1; 280 | c.gridx = 1; 281 | p.add(label, c); 282 | c.gridx = 2; 283 | p.add(comp, c); 284 | c.gridy++; 285 | } catch (IllegalArgumentException e) 286 | { 287 | e.printStackTrace(); 288 | } catch (IllegalAccessException e) 289 | { 290 | e.printStackTrace(); 291 | } 292 | } 293 | return c.gridy; 294 | } 295 | 296 | private Color levelColor(int level) 297 | { 298 | switch (level) 299 | { 300 | case Setting.LEVEL_NORMAL: 301 | return Color.ORANGE; 302 | case Setting.LEVEL_ADVANCED: 303 | return Color.YELLOW; 304 | case Setting.LEVEL_KITCHENSINK: 305 | return Color.RED; 306 | } 307 | return Color.WHITE; 308 | } 309 | 310 | private Component getSwingComponentForField(final Field f, Setting s) throws IllegalArgumentException, IllegalAccessException 311 | { 312 | if (f.getType() == Integer.TYPE) 313 | { 314 | if (s.enumName().equals("")) 315 | { 316 | JSpinner spinner = new JSpinner(new SpinnerNumberModel(f.getInt(null), (int) s.minValue(), (int) s.maxValue(), 1)); 317 | spinner.addChangeListener(new ChangeListener() 318 | { 319 | public void stateChanged(ChangeEvent e) 320 | { 321 | try 322 | { 323 | f.setInt(null, ((Integer) ((JSpinner) e.getSource()).getValue()).intValue()); 324 | CraftConfigLoader.saveConfig(null); 325 | } catch (Exception e1) 326 | { 327 | e1.printStackTrace(); 328 | } 329 | } 330 | }); 331 | return spinner; 332 | } else 333 | { 334 | Vector items = new Vector(); 335 | for (final Field enumField : CraftConfig.class.getFields()) 336 | { 337 | String name = enumField.getName(); 338 | if (name.startsWith(s.enumName() + "_")) 339 | { 340 | name = name.substring(name.indexOf("_") + 1); 341 | name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); 342 | name = name.replace('_', ' '); 343 | items.add(name); 344 | } 345 | } 346 | final JComboBox combo = new JComboBox(items); 347 | combo.setSelectedIndex(f.getInt(null)); 348 | combo.addActionListener(new ActionListener() 349 | { 350 | public void actionPerformed(ActionEvent e) 351 | { 352 | try 353 | { 354 | f.setInt(null, combo.getSelectedIndex()); 355 | CraftConfigLoader.saveConfig(null); 356 | } catch (Exception e1) 357 | { 358 | e1.printStackTrace(); 359 | } 360 | } 361 | }); 362 | return combo; 363 | } 364 | } else if (f.getType() == Double.TYPE) 365 | { 366 | JSpinner spinner = new JSpinner(new SpinnerNumberModel(f.getDouble(null), s.minValue(), s.maxValue(), 0.01)); 367 | spinner.addChangeListener(new ChangeListener() 368 | { 369 | public void stateChanged(ChangeEvent e) 370 | { 371 | try 372 | { 373 | f.setDouble(null, ((Double) ((JSpinner) e.getSource()).getValue()).doubleValue()); 374 | CraftConfigLoader.saveConfig(null); 375 | } catch (Exception e1) 376 | { 377 | e1.printStackTrace(); 378 | } 379 | } 380 | }); 381 | return spinner; 382 | } else if (f.getType() == Boolean.TYPE) 383 | { 384 | JCheckBox checkbox = new JCheckBox(); 385 | checkbox.setSelected(f.getBoolean(null)); 386 | checkbox.addActionListener(new ActionListener() 387 | { 388 | public void actionPerformed(ActionEvent e) 389 | { 390 | try 391 | { 392 | f.setBoolean(null, ((JCheckBox) e.getSource()).isSelected()); 393 | CraftConfigLoader.saveConfig(null); 394 | } catch (Exception e1) 395 | { 396 | e1.printStackTrace(); 397 | } 398 | } 399 | }); 400 | return checkbox; 401 | } else 402 | { 403 | Logger.error("Unknown field type for config window: " + f.getType()); 404 | } 405 | return null; 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/ui/LogWindow.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.ui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Dimension; 5 | 6 | import javax.swing.JFrame; 7 | import javax.swing.JLabel; 8 | import javax.swing.JProgressBar; 9 | import javax.swing.SwingUtilities; 10 | 11 | import daid.sliceAndDaid.config.CraftConfig; 12 | import daid.sliceAndDaid.util.Logger; 13 | import daid.sliceAndDaid.util.LoggingInterface; 14 | 15 | public class LogWindow extends JFrame implements LoggingInterface 16 | { 17 | private static final long serialVersionUID = 1L; 18 | 19 | private JLabel statusLabel; 20 | private JProgressBar progressBar; 21 | 22 | public LogWindow() 23 | { 24 | this.setTitle("SliceAndDaid - " + CraftConfig.VERSION); 25 | this.setResizable(false); 26 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 27 | 28 | this.setLayout(new BorderLayout()); 29 | 30 | statusLabel = new JLabel("SliceAndDaidSliceAndDaidSliceAndDaidSliceAndDaidSliceAndDaid"); 31 | statusLabel.setMinimumSize(new Dimension(200, statusLabel.getHeight())); 32 | this.add(statusLabel, BorderLayout.NORTH); 33 | 34 | progressBar = new JProgressBar(0, 2); 35 | progressBar.setIndeterminate(true); 36 | progressBar.setStringPainted(false); 37 | this.add(progressBar, BorderLayout.CENTER); 38 | 39 | this.pack(); 40 | this.setLocationRelativeTo(null); 41 | this.setVisible(true); 42 | 43 | Logger.register(this); 44 | } 45 | 46 | public void error(String error) 47 | { 48 | } 49 | 50 | public void message(String message) 51 | { 52 | } 53 | 54 | public void updateStatus(final String status) 55 | { 56 | SwingUtilities.invokeLater(new Runnable() 57 | { 58 | public void run() 59 | { 60 | statusLabel.setText(status); 61 | progressBar.setIndeterminate(true); 62 | progressBar.setStringPainted(false); 63 | LogWindow.this.repaint(); 64 | } 65 | }); 66 | } 67 | 68 | public void warning(String warning) 69 | { 70 | } 71 | 72 | public void dispose() 73 | { 74 | Logger.unRegister(this); 75 | super.dispose(); 76 | } 77 | 78 | public void setProgress(final int value, final int max) 79 | { 80 | SwingUtilities.invokeLater(new Runnable() 81 | { 82 | public void run() 83 | { 84 | progressBar.setIndeterminate(false); 85 | progressBar.setStringPainted(true); 86 | progressBar.setValue(value); 87 | progressBar.setMaximum(max); 88 | LogWindow.this.repaint(); 89 | } 90 | }); 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/ui/PreviewFrame.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.ui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Graphics; 6 | import java.awt.event.MouseEvent; 7 | import java.awt.event.MouseMotionListener; 8 | import java.util.Vector; 9 | 10 | import javax.swing.BoxLayout; 11 | import javax.swing.JFrame; 12 | import javax.swing.JLabel; 13 | import javax.swing.JPanel; 14 | import javax.swing.JSpinner; 15 | import javax.swing.SpinnerNumberModel; 16 | import javax.swing.event.ChangeEvent; 17 | import javax.swing.event.ChangeListener; 18 | 19 | import daid.sliceAndDaid.Layer; 20 | import daid.sliceAndDaid.Segment2D; 21 | import daid.sliceAndDaid.util.Vector2; 22 | 23 | public class PreviewFrame extends JFrame 24 | { 25 | private static final long serialVersionUID = 1L; 26 | private Vector layers; 27 | 28 | public class PreviewPanel extends JPanel implements MouseMotionListener 29 | { 30 | private static final long serialVersionUID = 1L; 31 | 32 | public int showLayer = 0; 33 | public double drawScale = 5.0; 34 | public double viewOffsetX, viewOffsetY; 35 | 36 | private int oldX, oldY; 37 | 38 | public PreviewPanel() 39 | { 40 | addMouseMotionListener(this); 41 | } 42 | 43 | public void paint(Graphics g) 44 | { 45 | super.paint(g); 46 | for (Segment2D s : layers.get(showLayer).modelSegmentList) 47 | { 48 | drawSegment(g, s); 49 | } 50 | for (Segment2D s = layers.get(showLayer).pathStart; s != null; s = s.getNext()) 51 | { 52 | drawSegment(g, s); 53 | } 54 | } 55 | 56 | private void drawSegment(Graphics g, Segment2D s) 57 | { 58 | switch (s.getType()) 59 | { 60 | case Segment2D.TYPE_MODEL_SLICE: 61 | g.setColor(Color.GREEN); 62 | break; 63 | case Segment2D.TYPE_PERIMETER: 64 | g.setColor(Color.BLACK); 65 | break; 66 | case Segment2D.TYPE_FILL: 67 | g.setColor(Color.YELLOW); 68 | break; 69 | case Segment2D.TYPE_MOVE: 70 | g.setColor(Color.BLUE); 71 | break; 72 | default: 73 | g.setColor(Color.RED); 74 | break; 75 | } 76 | drawModelLine(g, s.start, s.end); 77 | Vector2 center = s.start.add(s.end).div(2); 78 | Vector2 normal = center.add(s.getNormal().div(drawScale / 5)); 79 | drawModelLine(g, center, normal); 80 | drawModelLine(g, s.start, normal); 81 | if (s.getPrev() == null) 82 | drawModelCircle(g, s.start, 10); 83 | if (s.getNext() == null) 84 | drawModelCircle(g, s.end, 10); 85 | } 86 | 87 | private void drawModelLine(Graphics g, Vector2 start, Vector2 end) 88 | { 89 | g.drawLine((int) ((start.x + viewOffsetX) * drawScale) + this.getWidth() / 2, (int) ((start.y + viewOffsetY) * drawScale) + this.getHeight() / 2, (int) ((end.x + viewOffsetX) * drawScale) + this.getWidth() / 2, (int) ((end.y + viewOffsetY) * drawScale) + this.getHeight() / 2); 90 | } 91 | 92 | private void drawModelCircle(Graphics g, Vector2 center, int radius) 93 | { 94 | g.drawOval((int) ((center.x + viewOffsetX) * drawScale) + this.getWidth() / 2 - radius / 2, (int) ((center.y + viewOffsetY) * drawScale) + this.getHeight() / 2 - radius / 2, radius, radius); 95 | } 96 | 97 | public void mouseDragged(MouseEvent e) 98 | { 99 | viewOffsetX += (double) (e.getX() - oldX) / drawScale; 100 | viewOffsetY += (double) (e.getY() - oldY) / drawScale; 101 | repaint(); 102 | oldX = e.getX(); 103 | oldY = e.getY(); 104 | } 105 | 106 | public void mouseMoved(MouseEvent e) 107 | { 108 | oldX = e.getX(); 109 | oldY = e.getY(); 110 | } 111 | } 112 | 113 | public PreviewFrame(Vector layers) 114 | { 115 | final PreviewPanel viewPanel = new PreviewPanel(); 116 | JPanel actionPanel = new JPanel(); 117 | actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.X_AXIS)); 118 | this.setTitle("Preview"); 119 | this.layers = layers; 120 | 121 | final JSpinner layerSpinner = new JSpinner(new SpinnerNumberModel(viewPanel.showLayer, 0, layers.size() - 1, 1)); 122 | layerSpinner.addChangeListener(new ChangeListener() 123 | { 124 | public void stateChanged(ChangeEvent e) 125 | { 126 | viewPanel.showLayer = ((Integer) layerSpinner.getValue()).intValue(); 127 | viewPanel.repaint(); 128 | } 129 | }); 130 | final JSpinner zoomSpinner = new JSpinner(new SpinnerNumberModel(viewPanel.drawScale, 1.0, 200.0, 1.0)); 131 | zoomSpinner.addChangeListener(new ChangeListener() 132 | { 133 | public void stateChanged(ChangeEvent e) 134 | { 135 | viewPanel.drawScale = ((Double) zoomSpinner.getValue()).doubleValue(); 136 | viewPanel.repaint(); 137 | } 138 | }); 139 | 140 | actionPanel.add(new JLabel("Layer:")); 141 | actionPanel.add(layerSpinner); 142 | actionPanel.add(new JLabel("Zoom:")); 143 | actionPanel.add(zoomSpinner); 144 | 145 | this.setLayout(new BorderLayout()); 146 | this.add(viewPanel, BorderLayout.CENTER); 147 | this.add(actionPanel, BorderLayout.SOUTH); 148 | 149 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 150 | this.pack(); 151 | this.setSize(600, 600); 152 | this.setVisible(true); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/AABBTree.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | import java.util.Iterator; 4 | import java.util.Stack; 5 | 6 | /** 7 | * The Tree2D stores a binary AABB tree. AABB are rectangle 2D 'objects' The Tree2D allows a fast 8 | * query of all objects in an area. 9 | */ 10 | public class AABBTree 11 | { 12 | class TreeNode 13 | { 14 | public TreeNode parent; 15 | public TreeNode child1, child2; 16 | public AABBrect aabb; 17 | public int height; 18 | 19 | public TreeNode(AABBrect aabb) 20 | { 21 | this.aabb = aabb; 22 | if (aabb.node != null) 23 | throw new RuntimeException(); 24 | aabb.node = this; 25 | } 26 | 27 | public boolean isLeaf() 28 | { 29 | return child1 == null; 30 | } 31 | } 32 | 33 | private TreeNode root = null; 34 | 35 | public void insert(T e) 36 | { 37 | TreeNode leaf = new TreeNode(e); 38 | if (root == null) 39 | { 40 | root = leaf; 41 | return; 42 | } 43 | 44 | // Find the best sibling for this node 45 | TreeNode node = root; 46 | while (node.isLeaf() == false) 47 | { 48 | TreeNode child1 = node.child1; 49 | TreeNode child2 = node.child2; 50 | 51 | double area = node.aabb.getPerimeter(); 52 | 53 | AABBrect combinedAABB = node.aabb.combine(e); 54 | double combinedArea = combinedAABB.getPerimeter(); 55 | 56 | // Cost of creating a new parent for this node and the new leaf 57 | double cost = 2.0f * combinedArea; 58 | 59 | // Minimum cost of pushing the leaf further down the tree 60 | double inheritanceCost = 2.0f * (combinedArea - area); 61 | 62 | // Cost of descending into child1 63 | double cost1; 64 | if (child1.isLeaf()) 65 | { 66 | AABBrect aabb = e.combine(child1.aabb); 67 | cost1 = aabb.getPerimeter() + inheritanceCost; 68 | } else 69 | { 70 | AABBrect aabb = e.combine(child1.aabb); 71 | double oldArea = child1.aabb.getPerimeter(); 72 | double newArea = aabb.getPerimeter(); 73 | cost1 = (newArea - oldArea) + inheritanceCost; 74 | } 75 | 76 | // Cost of descending into child2 77 | double cost2; 78 | if (child2.isLeaf()) 79 | { 80 | AABBrect aabb = e.combine(child2.aabb); 81 | cost2 = aabb.getPerimeter() + inheritanceCost; 82 | } else 83 | { 84 | AABBrect aabb = e.combine(child2.aabb); 85 | double oldArea = child2.aabb.getPerimeter(); 86 | double newArea = aabb.getPerimeter(); 87 | cost2 = (newArea - oldArea) + inheritanceCost; 88 | } 89 | 90 | // Descend according to the minimum cost. 91 | if (cost < cost1 && cost < cost2) 92 | { 93 | break; 94 | } 95 | 96 | // Descend 97 | if (cost1 < cost2) 98 | { 99 | node = child1; 100 | } else 101 | { 102 | node = child2; 103 | } 104 | } 105 | 106 | TreeNode sibling = node; 107 | 108 | // Create a new parent. 109 | TreeNode oldParent = sibling.parent; 110 | TreeNode newParent = new TreeNode(e.combine(sibling.aabb)); 111 | newParent.parent = oldParent; 112 | newParent.height = sibling.height + 1; 113 | 114 | if (oldParent != null) 115 | { 116 | // The sibling was not the root. 117 | if (oldParent.child1 == sibling) 118 | { 119 | oldParent.child1 = newParent; 120 | } else 121 | { 122 | oldParent.child2 = newParent; 123 | } 124 | 125 | newParent.child1 = sibling; 126 | newParent.child2 = leaf; 127 | sibling.parent = newParent; 128 | leaf.parent = newParent; 129 | } else 130 | { 131 | // The sibling was the root. 132 | newParent.child1 = sibling; 133 | newParent.child2 = leaf; 134 | sibling.parent = newParent; 135 | leaf.parent = newParent; 136 | root = newParent; 137 | } 138 | 139 | // Walk back up the tree fixing heights and AABBs 140 | node = leaf.parent; 141 | while (node != null) 142 | { 143 | node = balance(node); 144 | 145 | TreeNode child1 = node.child1; 146 | TreeNode child2 = node.child2; 147 | 148 | node.height = 1 + Math.max(child1.height, child2.height); 149 | node.aabb = child1.aabb.combine(child2.aabb); 150 | 151 | node = node.parent; 152 | } 153 | } 154 | 155 | @SuppressWarnings("unchecked") 156 | public void remove(T e) 157 | { 158 | TreeNode leaf = e.node; 159 | e.node = null; 160 | if (leaf == root) 161 | { 162 | root = null; 163 | return; 164 | } 165 | 166 | TreeNode parent = leaf.parent; 167 | TreeNode grandParent = parent.parent; 168 | TreeNode sibling; 169 | if (parent.child1 == leaf) 170 | { 171 | sibling = parent.child2; 172 | } else 173 | { 174 | sibling = parent.child1; 175 | } 176 | 177 | if (grandParent != null) 178 | { 179 | // Destroy parent and connect sibling to grandParent. 180 | if (grandParent.child1 == parent) 181 | { 182 | grandParent.child1 = sibling; 183 | } else 184 | { 185 | grandParent.child2 = sibling; 186 | } 187 | sibling.parent = grandParent; 188 | 189 | // Adjust ancestor bounds. 190 | TreeNode index = grandParent; 191 | while (index != null) 192 | { 193 | index = balance(index); 194 | 195 | TreeNode child1 = index.child1; 196 | TreeNode child2 = index.child2; 197 | 198 | index.aabb = child1.aabb.combine(child2.aabb); 199 | index.height = 1 + Math.max(child1.height, child2.height); 200 | 201 | index = index.parent; 202 | } 203 | } else 204 | { 205 | root = sibling; 206 | sibling.parent = null; 207 | } 208 | } 209 | 210 | private TreeNode balance(TreeNode A) 211 | { 212 | if (A.isLeaf() || A.height < 2) 213 | { 214 | return A; 215 | } 216 | 217 | TreeNode B = A.child1; 218 | TreeNode C = A.child2; 219 | 220 | int balance = C.height - B.height; 221 | 222 | // Rotate C up 223 | if (balance > 1) 224 | { 225 | TreeNode F = C.child1; 226 | TreeNode G = C.child2; 227 | 228 | // Swap A and C 229 | C.child1 = A; 230 | C.parent = A.parent; 231 | A.parent = C; 232 | 233 | // A's old parent should point to C 234 | if (C.parent != null) 235 | { 236 | if (C.parent.child1 == A) 237 | { 238 | C.parent.child1 = C; 239 | } else 240 | { 241 | C.parent.child2 = C; 242 | } 243 | } else 244 | { 245 | root = C; 246 | } 247 | 248 | // Rotate 249 | if (F.height > G.height) 250 | { 251 | C.child2 = F; 252 | A.child2 = G; 253 | G.parent = A; 254 | A.aabb = B.aabb.combine(G.aabb); 255 | C.aabb = A.aabb.combine(F.aabb); 256 | 257 | A.height = 1 + Math.max(B.height, G.height); 258 | C.height = 1 + Math.max(A.height, F.height); 259 | } else 260 | { 261 | C.child2 = G; 262 | A.child2 = F; 263 | F.parent = A; 264 | A.aabb = B.aabb.combine(F.aabb); 265 | C.aabb = A.aabb.combine(G.aabb); 266 | 267 | A.height = 1 + Math.max(B.height, F.height); 268 | C.height = 1 + Math.max(A.height, G.height); 269 | } 270 | 271 | return C; 272 | } 273 | 274 | // Rotate B up 275 | if (balance < -1) 276 | { 277 | TreeNode D = B.child1; 278 | TreeNode E = B.child2; 279 | 280 | // Swap A and B 281 | B.child1 = A; 282 | B.parent = A.parent; 283 | A.parent = B; 284 | 285 | // A's old parent should point to B 286 | if (B.parent != null) 287 | { 288 | if (B.parent.child1 == A) 289 | { 290 | B.parent.child1 = B; 291 | } else 292 | { 293 | B.parent.child2 = B; 294 | } 295 | } else 296 | { 297 | root = B; 298 | } 299 | 300 | // Rotate 301 | if (D.height > E.height) 302 | { 303 | B.child2 = D; 304 | A.child1 = E; 305 | E.parent = A; 306 | A.aabb = C.aabb.combine(E.aabb); 307 | B.aabb = A.aabb.combine(D.aabb); 308 | 309 | A.height = 1 + Math.max(C.height, E.height); 310 | B.height = 1 + Math.max(A.height, D.height); 311 | } else 312 | { 313 | B.child2 = E; 314 | A.child1 = D; 315 | D.parent = A; 316 | A.aabb = C.aabb.combine(D.aabb); 317 | B.aabb = A.aabb.combine(E.aabb); 318 | 319 | A.height = 1 + Math.max(C.height, D.height); 320 | B.height = 1 + Math.max(A.height, E.height); 321 | } 322 | 323 | return B; 324 | } 325 | 326 | return A; 327 | } 328 | 329 | public class Tree2Dquery implements Iterable 330 | { 331 | private AABBrect area; 332 | 333 | public Tree2Dquery(AABBrect area) 334 | { 335 | this.area = area; 336 | } 337 | 338 | public Iterator iterator() 339 | { 340 | return new Tree2Diterator(area); 341 | } 342 | } 343 | 344 | private class Tree2Diterator implements Iterator 345 | { 346 | private AABBrect area; 347 | private Stack stack; 348 | private T ret; 349 | 350 | public Tree2Diterator(AABBrect area) 351 | { 352 | this.area = area; 353 | stack = new Stack(); 354 | if (root != null) 355 | stack.add(root); 356 | } 357 | 358 | @SuppressWarnings("unchecked") 359 | public boolean hasNext() 360 | { 361 | if (ret != null) 362 | return true; 363 | while (stack.size() > 0) 364 | { 365 | TreeNode n = stack.pop(); 366 | if (n.aabb.overlap(area)) 367 | { 368 | if (n.isLeaf()) 369 | { 370 | ret = (T) n.aabb; 371 | return true; 372 | } else 373 | { 374 | stack.push(n.child1); 375 | stack.push(n.child2); 376 | } 377 | } 378 | } 379 | return false; 380 | } 381 | 382 | public T next() 383 | { 384 | if (!hasNext()) 385 | return null; 386 | T r = ret; 387 | ret = null; 388 | return r; 389 | } 390 | 391 | public void remove() 392 | { 393 | } 394 | } 395 | 396 | public Tree2Dquery query(AABBrect treeAABB) 397 | { 398 | return new Tree2Dquery(treeAABB); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/AABBrect.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | public class AABBrect 4 | { 5 | private Vector2 lowerBound, upperBound; 6 | 7 | @SuppressWarnings("unchecked") 8 | public AABBTree.TreeNode node; 9 | 10 | public AABBrect(Vector2 p1, Vector2 p2) 11 | { 12 | lowerBound = p1; 13 | upperBound = p2; 14 | } 15 | 16 | public AABBrect(AABBrect aabb) 17 | { 18 | lowerBound = aabb.lowerBound; 19 | upperBound = aabb.upperBound; 20 | } 21 | 22 | public AABBrect(Vector2 p1, Vector2 p2, double extend) 23 | { 24 | lowerBound = new Vector2(Math.min(p1.x, p2.x) - extend, Math.min(p1.y, p2.y) - extend); 25 | upperBound = new Vector2(Math.max(p1.x, p2.x) + extend, Math.max(p1.y, p2.y) + extend); 26 | } 27 | 28 | public double getPerimeter() 29 | { 30 | double w = upperBound.x - lowerBound.x; 31 | double h = upperBound.y - lowerBound.y; 32 | return (w + h) * 2.0; 33 | } 34 | 35 | public void updateAABB(Vector2 p1, Vector2 p2, double extend) 36 | { 37 | if (node != null) 38 | throw new UnsupportedOperationException("Update on AABBrect while in a AABBTree"); 39 | lowerBound = new Vector2(Math.min(p1.x, p2.x) - extend, Math.min(p1.y, p2.y) - extend); 40 | upperBound = new Vector2(Math.max(p1.x, p2.x) + extend, Math.max(p1.y, p2.y) + extend); 41 | } 42 | 43 | public AABBrect combine(AABBrect e) 44 | { 45 | return new AABBrect(new Vector2(Math.min(lowerBound.x, e.lowerBound.x), Math.min(lowerBound.y, e.lowerBound.y)), new Vector2(Math.max(upperBound.x, e.upperBound.x), Math.max(upperBound.y, e.upperBound.y))); 46 | } 47 | 48 | public void addAABB(AABBrect a) 49 | { 50 | if (node != null) 51 | throw new UnsupportedOperationException("addAABB on AABBrect while in a AABBTree"); 52 | lowerBound = new Vector2(Math.min(lowerBound.x, a.lowerBound.x), Math.min(lowerBound.y, a.lowerBound.y)); 53 | upperBound = new Vector2(Math.max(upperBound.x, a.upperBound.x), Math.max(upperBound.y, a.upperBound.y)); 54 | } 55 | 56 | public boolean overlap(AABBrect t) 57 | { 58 | if (t.lowerBound.x - upperBound.x > 0.0f || t.lowerBound.y - upperBound.y > 0.0f) 59 | return false; 60 | 61 | if (lowerBound.x - t.upperBound.x > 0.0f || lowerBound.y - t.upperBound.y > 0.0f) 62 | return false; 63 | 64 | return true; 65 | } 66 | 67 | public double getAABBDist(AABBrect t) 68 | { 69 | return upperBound.add(lowerBound).div(2).sub(t.upperBound.add(t.lowerBound).div(2)).vSize(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/GCodeFile.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.text.DecimalFormat; 6 | 7 | import daid.sliceAndDaid.config.CraftConfig; 8 | 9 | public class GCodeFile 10 | { 11 | private FileWriter fileWriter; 12 | private double totalExtruderValue = 0; 13 | private double buildTime = 0; 14 | private double lastFeedrate = -1; 15 | private Vector3 oldPos = new Vector3(); 16 | private DecimalFormat xyzFormat, eFormat, fFormat; 17 | 18 | public GCodeFile(FileWriter fileWriter) 19 | { 20 | this.fileWriter = fileWriter; 21 | xyzFormat = new DecimalFormat("#.##"); 22 | eFormat = new DecimalFormat("#.###"); 23 | fFormat = new DecimalFormat("#.#"); 24 | } 25 | 26 | public void writeMoveZ(double z, double feedRate, String comment) throws IOException 27 | { 28 | switch (CraftConfig.gcodeType) 29 | { 30 | case CraftConfig.GCODE_FULL: 31 | fileWriter.write("G1 Z" + xyzFormat.format(z) + " F" + fFormat.format(feedRate * 60) + "; " + comment + "\n"); 32 | break; 33 | case CraftConfig.GCODE_COMPACT: 34 | if (feedRate != lastFeedrate) 35 | fileWriter.write("G1 Z" + xyzFormat.format(z) + " F" + fFormat.format(feedRate * 60) + "\n"); 36 | else 37 | fileWriter.write("G1 Z" + xyzFormat.format(z) + "\n"); 38 | break; 39 | case CraftConfig.GCODE_TINY_COMPACT: 40 | if (feedRate != lastFeedrate) 41 | fileWriter.write("G1 Z" + xyzFormat.format(z) + " F" + fFormat.format(feedRate * 60) + "\n"); 42 | else 43 | fileWriter.write("G1 Z" + xyzFormat.format(z) + "\n"); 44 | break; 45 | } 46 | 47 | doMove(oldPos.x, oldPos.y, z, feedRate); 48 | } 49 | 50 | public void writeMoveXY(double x, double y, double feedRate, String comment) throws IOException 51 | { 52 | switch (CraftConfig.gcodeType) 53 | { 54 | case CraftConfig.GCODE_FULL: 55 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + " F" + fFormat.format(feedRate * 60) + "; " + comment + "\n"); 56 | break; 57 | case CraftConfig.GCODE_COMPACT: 58 | if (feedRate != lastFeedrate) 59 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + " F" + fFormat.format(feedRate * 60) + "\n"); 60 | else 61 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + "\n"); 62 | break; 63 | case CraftConfig.GCODE_TINY_COMPACT: 64 | if (feedRate != lastFeedrate) 65 | fileWriter.write("G1X" + xyzFormat.format(x) + "Y" + xyzFormat.format(y) + "F" + fFormat.format(feedRate * 60) + "\n"); 66 | else 67 | fileWriter.write("G1X" + xyzFormat.format(x) + "Y" + xyzFormat.format(y) + "\n"); 68 | break; 69 | } 70 | 71 | doMove(x, y, oldPos.z, feedRate); 72 | } 73 | 74 | public void writeMoveXYE(double x, double y, double e, double feedRate, String comment) throws IOException 75 | { 76 | totalExtruderValue += e; 77 | switch (CraftConfig.gcodeType) 78 | { 79 | case CraftConfig.GCODE_FULL: 80 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + " E" + eFormat.format(totalExtruderValue) + " F" + fFormat.format(feedRate * 60) + "; " + comment + "\n"); 81 | break; 82 | case CraftConfig.GCODE_COMPACT: 83 | if (lastFeedrate != feedRate) 84 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + " E" + eFormat.format(totalExtruderValue) + " F" + fFormat.format(feedRate * 60) + "\n"); 85 | else 86 | fileWriter.write("G1 X" + xyzFormat.format(x) + " Y" + xyzFormat.format(y) + " E" + eFormat.format(totalExtruderValue) + "\n"); 87 | break; 88 | case CraftConfig.GCODE_TINY_COMPACT: 89 | if (lastFeedrate != feedRate) 90 | fileWriter.write("G1X" + xyzFormat.format(x) + "Y" + xyzFormat.format(y) + "E" + eFormat.format(totalExtruderValue) + "F" + fFormat.format(feedRate * 60) + "\n"); 91 | else 92 | fileWriter.write("G1X" + xyzFormat.format(x) + "Y" + xyzFormat.format(y) + "E" + eFormat.format(totalExtruderValue) + "\n"); 93 | break; 94 | } 95 | 96 | doMove(x, y, oldPos.z, feedRate); 97 | } 98 | 99 | public void writeComment(String string) throws IOException 100 | { 101 | switch (CraftConfig.gcodeType) 102 | { 103 | case CraftConfig.GCODE_FULL: 104 | fileWriter.write("; " + string + "\n"); 105 | break; 106 | case CraftConfig.GCODE_COMPACT: 107 | break; 108 | case CraftConfig.GCODE_TINY_COMPACT: 109 | break; 110 | } 111 | } 112 | 113 | public void write(String string) throws IOException 114 | { 115 | fileWriter.write(string + "\n"); 116 | } 117 | 118 | public void close() throws IOException 119 | { 120 | fileWriter.close(); 121 | } 122 | 123 | private void doMove(double x, double y, double z, double feedRate) 124 | { 125 | double dist = oldPos.sub(new Vector3(x, y, z)).vSize(); 126 | buildTime += dist / feedRate; 127 | lastFeedrate = feedRate; 128 | } 129 | 130 | public double getBuildTime() 131 | { 132 | return buildTime; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/Logger.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * Logging class, has static functions for logging. 7 | * 8 | * TODO: Different log listeners can connect to this logging service. So the GUI version can show a nice progress dialog. 9 | */ 10 | public class Logger 11 | { 12 | private static HashSet loggers = new HashSet(); 13 | 14 | public static void updateStatus(String status) 15 | { 16 | System.out.println(status); 17 | for (LoggingInterface li : loggers) 18 | li.updateStatus(status); 19 | } 20 | 21 | public static void message(String message) 22 | { 23 | System.out.println(message); 24 | for (LoggingInterface li : loggers) 25 | li.message(message); 26 | } 27 | 28 | public static void warning(String warning) 29 | { 30 | System.err.println(warning); 31 | for (LoggingInterface li : loggers) 32 | li.warning(warning); 33 | } 34 | 35 | public static void error(String error) 36 | { 37 | System.err.println(error); 38 | for (LoggingInterface li : loggers) 39 | li.error(error); 40 | } 41 | 42 | public static void setProgress(int value, int max) 43 | { 44 | // System.out.println(value + "/" + max); 45 | for (LoggingInterface li : loggers) 46 | li.setProgress(value, max); 47 | } 48 | 49 | public static void register(LoggingInterface obj) 50 | { 51 | loggers.add(obj); 52 | } 53 | 54 | public static void unRegister(LoggingInterface obj) 55 | { 56 | loggers.remove(obj); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/LoggingInterface.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | public interface LoggingInterface 4 | { 5 | void updateStatus(String status); 6 | void message(String message); 7 | void warning(String warning); 8 | void error(String error); 9 | void setProgress(int value, int max); 10 | } 11 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/Triangle.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | import daid.sliceAndDaid.Segment2D; 4 | 5 | /** 6 | * The triangle class represents a 3D triangle in a 3D model 7 | */ 8 | public class Triangle 9 | { 10 | public Vector3[] point = new Vector3[3]; 11 | 12 | public Segment2D project2D(double layerZ) 13 | { 14 | Segment2D ret = null; 15 | 16 | if (point[0].z < layerZ && point[1].z >= layerZ && point[2].z >= layerZ) 17 | ret = setSegment(ret, layerZ, point[0], point[2], point[1]); 18 | else if (point[0].z > layerZ && point[1].z <= layerZ && point[2].z <= layerZ) 19 | ret = setSegment(ret, layerZ, point[0], point[1], point[2]); 20 | 21 | else if (point[1].z < layerZ && point[0].z >= layerZ && point[2].z >= layerZ) 22 | ret = setSegment(ret, layerZ, point[1], point[0], point[2]); 23 | else if (point[1].z > layerZ && point[0].z <= layerZ && point[2].z <= layerZ) 24 | ret = setSegment(ret, layerZ, point[1], point[2], point[0]); 25 | 26 | else if (point[2].z < layerZ && point[1].z >= layerZ && point[0].z >= layerZ) 27 | ret = setSegment(ret, layerZ, point[2], point[1], point[0]); 28 | else if (point[2].z > layerZ && point[1].z <= layerZ && point[0].z <= layerZ) 29 | ret = setSegment(ret, layerZ, point[2], point[0], point[1]); 30 | else 31 | { 32 | // Logger.error("Cannot handle triangle:\n" + point[0] + "\n" + point[1] + "\n" + 33 | // point[2] + "\non Z: " + layerZ); 34 | return null; 35 | } 36 | if (Double.isNaN(ret.start.x) || Double.isNaN(ret.end.x)) 37 | { 38 | Logger.error("Error on triangle:\n" + point[0] + "\n" + point[1] + "\n" + point[2] + "\non Z: " + layerZ); 39 | } 40 | 41 | return ret; 42 | } 43 | 44 | private Segment2D setSegment(Segment2D ret, double layerZ, Vector3 v0, Vector3 v1, Vector3 v2) 45 | { 46 | double a1 = (layerZ - v0.z) / (v1.z - v0.z); 47 | double a2 = (layerZ - v0.z) / (v2.z - v0.z); 48 | Vector2 start = new Vector2(v0.x + (v1.x - v0.x) * a1, v0.y + (v1.y - v0.y) * a1); 49 | Vector2 end = new Vector2(v0.x + (v2.x - v0.x) * a2, v0.y + (v2.y - v0.y) * a2); 50 | return new Segment2D(Segment2D.TYPE_MODEL_SLICE, start, end); 51 | } 52 | 53 | public Vector3 getNormal() 54 | { 55 | return point[1].sub(point[0]).cross(point[2].sub(point[0])).normal(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/Vector2.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | public class Vector2 4 | { 5 | public final double x, y; 6 | 7 | public Vector2(double x, double y) 8 | { 9 | this.x = x; 10 | this.y = y; 11 | if (Double.isNaN(x) || Double.isNaN(y)) 12 | throw new RuntimeException("Vector has NaN component..."); 13 | } 14 | 15 | public Vector2 add(Vector2 v) 16 | { 17 | return new Vector2(x + v.x, y + v.y); 18 | } 19 | 20 | public Vector2 sub(Vector2 v) 21 | { 22 | return new Vector2(x - v.x, y - v.y); 23 | } 24 | 25 | public Vector2 div(double f) 26 | { 27 | return new Vector2(x / f, y / f); 28 | } 29 | 30 | public Vector2 mul(double f) 31 | { 32 | return new Vector2(x * f, y * f); 33 | } 34 | 35 | public Vector2 crossZ() 36 | { 37 | return new Vector2(y, -x); 38 | } 39 | 40 | public double dot(Vector2 v) 41 | { 42 | return x * v.x + y * v.y; 43 | } 44 | 45 | public boolean asGoodAsEqual(Vector2 v) 46 | { 47 | return (Math.abs(x - v.x) + Math.abs(y - v.y)) < 0.00001; 48 | } 49 | 50 | public String toString() 51 | { 52 | return x + "," + y; 53 | } 54 | 55 | /** 56 | * Returns a normalized vector with a length of 1, having the same direction as the origonal vector. 57 | */ 58 | public Vector2 normal() 59 | { 60 | double d = vSize(); 61 | if (d < 0.0000001) 62 | return new Vector2(0, 0); 63 | return new Vector2(x / d, y / d); 64 | } 65 | 66 | /** 67 | * Returns the length of the vector. 68 | */ 69 | public double vSize() 70 | { 71 | return Math.sqrt(x * x + y * y); 72 | } 73 | 74 | /** 75 | * Returns the squared length of the vector (faster then vSize()) 76 | */ 77 | public double vSize2() 78 | { 79 | return x * x + y * y; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/daid/sliceAndDaid/util/Vector3.java: -------------------------------------------------------------------------------- 1 | package daid.sliceAndDaid.util; 2 | 3 | public class Vector3 4 | { 5 | public double x, y, z; 6 | 7 | public Vector3() 8 | { 9 | } 10 | 11 | public Vector3(double x, double y, double z) 12 | { 13 | this.x = x; 14 | this.y = y; 15 | this.z = z; 16 | } 17 | 18 | public String toString() 19 | { 20 | return x + "," + y + "," + z; 21 | } 22 | 23 | public void addToSelf(Vector3 v) 24 | { 25 | x += v.x; 26 | y += v.y; 27 | z += v.z; 28 | } 29 | 30 | public Vector3 sub(Vector3 v) 31 | { 32 | return new Vector3(x - v.x, y - v.y, z - v.z); 33 | } 34 | 35 | public Vector3 cross(Vector3 v) 36 | { 37 | return new Vector3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); 38 | } 39 | 40 | public double dot(Vector3 v) 41 | { 42 | return x * v.x + y * v.y + z * v.z; 43 | } 44 | 45 | public Vector3 normal() 46 | { 47 | return div(vSize()); 48 | } 49 | 50 | public Vector3 div(double f) 51 | { 52 | return new Vector3(x / f, y / f, z / f); 53 | } 54 | 55 | public double vSize() 56 | { 57 | return Math.sqrt(x * x + y * y + z * z); 58 | } 59 | 60 | public double vSize2() 61 | { 62 | return x * x + y * y + z * z; 63 | } 64 | } 65 | --------------------------------------------------------------------------------