diff --git a/FileLoading.pde b/FileLoading.pde new file mode 100644 index 0000000..b8217a2 --- /dev/null +++ b/FileLoading.pde @@ -0,0 +1,436 @@ +/** + Polargraph controller + Copyright Sandy Noble 2018. + + This file is part of Polargraph Controller. + + Polargraph Controller is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Polargraph Controller is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Polargraph Controller. If not, see . + + Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. + Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. + + This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. + + sandy.noble@gmail.com + http://www.polargraph.co.uk/ + https://github.com/euphy/polargraphcontroller +*/ + + + +void loadImageWithFileChooser() +{ + SwingUtilities.invokeLater(new Runnable() + { + public void run() { + JFileChooser fc = new JFileChooser(); + if (lastImageDirectory != null) fc.setCurrentDirectory(lastImageDirectory); + fc.setFileFilter(new ImageFileFilter()); + fc.setDialogTitle("Choose an image file..."); + + int returned = fc.showOpenDialog(frame); + + lastImageDirectory = fc.getCurrentDirectory(); + + if (returned == JFileChooser.APPROVE_OPTION) + { + File file = fc.getSelectedFile(); + // see if it's an image + PImage img = loadImage(file.getPath()); + if (img != null) + { + img = null; + getDisplayMachine().loadNewImageFromFilename(file.getPath()); + if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) + { + getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); + } + } + } + } + }); +} + +class ImageFileFilter extends javax.swing.filechooser.FileFilter +{ + public boolean accept(File file) { + String filename = file.getName(); + filename.toLowerCase(); + if (file.isDirectory() || filename.endsWith(".png") || filename.endsWith(".jpg") || filename.endsWith(".jpeg")) + return true; + else + return false; + } + public String getDescription() { + return "Image files (PNG or JPG)"; + } +} + +void loadVectorWithFileChooser() +{ + SwingUtilities.invokeLater(new Runnable() + { + public void run() { + JFileChooser fc = new JFileChooser(); + if (lastImageDirectory != null) + { + fc.setCurrentDirectory(lastImageDirectory); + } + + fc.setFileFilter(new VectorFileFilter()); + fc.setDialogTitle("Choose a vector file..."); + int returned = fc.showOpenDialog(frame); + lastImageDirectory = fc.getCurrentDirectory(); + + if (returned == JFileChooser.APPROVE_OPTION) + { + File file = fc.getSelectedFile(); + if (file.exists()) + { + RShape shape = loadShapeFromFile(file.getPath()); + if (shape != null) + { + setVectorFilename(file.getPath()); + setVectorShape(shape); + } + else + { + println("File not found (" + file.getPath() + ")"); + } + } + } + } + } + ); +} + +class VectorFileFilter extends javax.swing.filechooser.FileFilter +{ + public boolean accept(File file) { + String filename = file.getName(); + filename.toLowerCase(); + if (file.isDirectory() || filename.endsWith(".svg") || isGCodeExtension(filename)) + return true; + else + return false; + } + public String getDescription() { + return "Vector graphic files (SVG, GCode)"; + } +} + +void loadNewPropertiesFilenameWithFileChooser() +{ + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + JFileChooser fc = new JFileChooser(); + if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); + fc.setFileFilter(new PropertiesFileFilter()); + + fc.setDialogTitle("Choose a config file..."); + + int returned = fc.showOpenDialog(frame); + + lastPropertiesDirectory = fc.getCurrentDirectory(); + + if (returned == JFileChooser.APPROVE_OPTION) + { + File file = fc.getSelectedFile(); + if (file.exists()) + { + println("New properties file exists."); + newPropertiesFilename = file.toString(); + println("new propertiesFilename: "+ newPropertiesFilename); + propertiesFilename = newPropertiesFilename; + // clear old properties. + props = null; + loadFromPropertiesFile(); + + // set values of number spinners etc + updateNumberboxValues(); + } + } + } + }); +} + +class PropertiesFileFilter extends javax.swing.filechooser.FileFilter +{ + public boolean accept(File file) { + String filename = file.getName(); + filename.toLowerCase(); + if (file.isDirectory() || filename.endsWith(".properties.txt")) + return true; + else + return false; + } + public String getDescription() { + return "Properties files (*.properties.txt)"; + } +} + +void saveNewPropertiesFileWithFileChooser() +{ + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + JFileChooser fc = new JFileChooser(); + if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); + fc.setFileFilter(new PropertiesFileFilter()); + + fc.setDialogTitle("Enter a config file name..."); + + int returned = fc.showSaveDialog(frame); + if (returned == JFileChooser.APPROVE_OPTION) + { + File file = fc.getSelectedFile(); + newPropertiesFilename = file.toString(); + newPropertiesFilename.toLowerCase(); + if (!newPropertiesFilename.endsWith(".properties.txt")) + newPropertiesFilename+=".properties.txt"; + + println("new propertiesFilename: "+ newPropertiesFilename); + propertiesFilename = newPropertiesFilename; + savePropertiesFile(); + // clear old properties. + props = null; + loadFromPropertiesFile(); + } + } + }); +} + + + +RShape loadShapeFromFile(String filename) { + RShape sh = null; + if (filename.toLowerCase().endsWith(".svg")) { + sh = RG.loadShape(filename); + } + else if (isGCodeExtension(filename)) { + sh = loadShapeFromGCodeFile(filename); + } + return sh; +} + + +boolean isGCodeExtension(String filename) { + return (filename.toLowerCase().endsWith(".gcode") || filename.toLowerCase().endsWith(".g") || filename.toLowerCase().endsWith(".ngc") || filename.toLowerCase().endsWith(".txt")); +} + + +int countLines(String filename) throws IOException { + InputStream is = new BufferedInputStream(new FileInputStream(filename)); + try { + byte[] c = new byte[1024]; + int count = 0; + int readChars = 0; + boolean empty = true; + while ((readChars = is.read(c)) != -1) { + empty = false; + for (int i = 0; i < readChars; ++i) { + if (c[i] == '\n') { + ++count; + } + } + } + return (count == 0 && !empty) ? 1 : count+1; + } finally { + is.close(); + } +} + +RShape loadShapeFromGCodeFile(String filename) { + noLoop(); + RShape parent = null; + BufferedReader reader = null; + long totalPoints = 0; + long time = millis(); + long countLines = 0; + + try { + countLines = countLines(filename); + println("" + countLines + " lines found."); + if (countLines < 1) { + throw new IOException("No lines found in GCode file."); + } + reader = createReader(filename); + parent = new RShape(); + String line; + boolean drawLine = false; + int gCodeZAxisChanges = 0; + + long lineNo = 0; + float lastPercent = 0.0f; + boolean reportStatus = true; + while ((line = reader.readLine ()) != null) { + lineNo++; +// println("Line: " + line); + + if (reportStatus) { + float percent = ((float)lineNo / (float)countLines) * 100.0; + println("----" + percent + "% of the way through."); + lastPercent = percent; + } + + if (line.toUpperCase().startsWith("G")) { + if (reportStatus) { + println(new StringBuilder().append(lineNo).append(" of ").append(countLines).append(": ").append(line).append(". Points: ").append(totalPoints).toString()); + long free = Runtime.getRuntime().freeMemory(); + long maximum = Runtime.getRuntime().maxMemory(); + println(new StringBuilder().append("Free: ").append(free).append(", max: ").append(maximum).toString()); + } + + Map ins = null; + try { + ins = unpackGCodeInstruction(line); + } + catch (Exception e) { + println(e.toString()); + continue; + } +// println("Ins: " + ins); + Integer code = Math.round(ins.get("G")); + + Float z = ins.get("Z"); + if (z != null) { + gCodeZAxisChanges++; + if (gCodeZAxisChanges == 2) { + println("Assume second z axis change is to drop the pen to start drawing " + z); + gcodeZAxisDrawingHeight = z; + drawLine = true; + } + else if (gCodeZAxisChanges > 2) { + drawLine = isGCodeZAxisForDrawing(z); + } + else { + println("Assume first z axis change is to RAISE the pen " + z); + drawLine = false; + } + } + + Float x = ins.get("X"); + Float y = ins.get("Y"); + if (x != null && y == null) { + // move x axis only, use y of last + RPoint[][] points = parent.getPointsInPaths(); + RPoint rp = points[points.length-1][points[points.length-1].length-1]; + y = rp.y; + } + else if (x == null && y != null) { + // move y axis only, use x of last + RPoint[][] points = parent.getPointsInPaths(); + RPoint rp = points[points.length-1][points[points.length-1].length-1]; + x = rp.x; + } + + if (x != null && y != null) { + // move both x and y axis + if (drawLine) { + parent.addLineTo(x, y); + } + else { + parent.addMoveTo(x, y); + } + } + } + else { + + } + + if ((millis() - time) > 500) { + time = millis(); + reportStatus = true; + } + else { + reportStatus = false; + } + + if (lineNo == (countLines-1)) { + reportStatus = true; + } + + } + } + catch (IOException e) { + println("IOExecption reading lines from the gcode file " + filename); + e.printStackTrace(); + } + finally { + try { + reader.close(); + } + catch (IOException e) { + println("IOException closing the gcode file " + filename); + e.printStackTrace(); + } + } + + RPoint[][] points = parent.getPointsInPaths(); + totalPoints = 0; + if (points != null) { + for (int i = 0; i unpackGCodeInstruction(String line) throws Exception { + Map instruction = new HashMap(4); + try { + String[] splitted = line.trim().split(" "); + for (int i = 0; i < splitted.length; i++) { + // remove ; character + splitted[i] = splitted[i].replace(";", ""); + String axis = splitted[i].substring(0, 1); + String sanitisedValue = splitted[i].substring(1); + sanitisedValue = sanitisedValue.replace(",", "."); + Float value = Float.parseFloat(sanitisedValue); + if ("X".equalsIgnoreCase(axis) || "Y".equalsIgnoreCase(axis) || "Z".equalsIgnoreCase(axis) || "G".equalsIgnoreCase(axis)) { + instruction.put(axis.toUpperCase(), value); + } + } +// println("instruction: " + instruction); + if (instruction.isEmpty()) { + throw new Exception("Empty instruction"); + } + } + catch (NumberFormatException nfe) { + println("Number format exception: " + nfe.getMessage()); + } + catch (Exception e) { + println("e: " + e); + throw new Exception("Exception while reading the lines from a gcode file: " + line + ", " + e.getMessage()); + } + + return instruction; +} diff --git a/Machine.pde b/Machine.pde index f5d6c37..df86936 100644 --- a/Machine.pde +++ b/Machine.pde @@ -186,9 +186,15 @@ class Machine return mmInt; } + public float inMMFloat(float steps) + { + double mm = steps / getStepsPerMM(); + return (float) mm; + } + public PVector inMM (PVector steps) { - PVector mm = new PVector(inMM(steps.x), inMM(steps.y)); + PVector mm = new PVector(inMMFloat(steps.x), inMMFloat(steps.y)); return mm; } @@ -322,8 +328,8 @@ class Machine public PVector asCartesianCoords(PVector pgCoords) { - float calcX = int((pow(getWidth(), 2) - pow(pgCoords.y, 2) + pow(pgCoords.x, 2)) / (getWidth()*2)); - float calcY = int(sqrt(pow(pgCoords.x,2)-pow(calcX,2))); + float calcX = (pow(getWidth(), 2.0) - pow(pgCoords.y, 2.0) + pow(pgCoords.x, 2.0)) / (getWidth()*2.0); + float calcY = sqrt(pow(pgCoords.x,2.0)-pow(calcX,2.0)); PVector vect = new PVector(calcX, calcY); return vect; } diff --git a/Panel.pde b/Panel.pde index bd3e281..d75408c 100644 --- a/Panel.pde +++ b/Panel.pde @@ -133,7 +133,6 @@ class Panel { for (Controller c : this.getControls()) { -// println("Control: " + c.getName()); PVector pos = getControlPositions().get(c.getName()); float x = pos.x+getOutline().getLeft(); float y = pos.y+getOutline().getTop(); @@ -165,6 +164,13 @@ class Panel { locked = true; } + + // if there's no vector loaded, then hide vector controls + if (getControlsToLockIfVectorNotLoaded().contains(c.getName()) && vectorFilename == null) + { + locked = true; + } + if (c.getName().equals(MODE_LOAD_VECTOR_FILE)) { @@ -211,6 +217,7 @@ class Panel this.getOutline().setHeight(getMinimumHeight()); else this.getOutline().setHeight(h); + setControlPositions(buildControlPositionsForPanel(this)); float left = 0.0; diff --git a/controlsActions.pde b/controlsActions.pde index 409c737..9be876f 100644 --- a/controlsActions.pde +++ b/controlsActions.pde @@ -323,6 +323,7 @@ void button_mode_loadImage() getDisplayMachine().setImageFilename(null); } } + void button_mode_loadVectorFile() { if (getVectorShape() == null) @@ -336,6 +337,7 @@ void button_mode_loadVectorFile() vectorFilename = null; } } + void numberbox_mode_pixelBrightThreshold(float value) { pixelExtractBrightThreshold = (int) value; @@ -730,17 +732,11 @@ void numberbox_mode_previewCordOffsetValue(int value) previewQueue(true); } -void button_mode_cycleDensityPreviewStyle() +void dropdown_mode_cycleDensityPreviewStyle(int index) { - Controller c = cp5.getController(MODE_CYCLE_DENSITY_PREVIEW_STYLE); - c.setLabel(this.controlLabels.get(MODE_CYCLE_DENSITY_PREVIEW_STYLE) + ": " + densityPreviewStyle); - - if (densityPreviewStyle == DENSITY_PREVIEW_STYLE_COUNT) { - densityPreviewStyle = 0; - } - else { - densityPreviewStyle++; - } + println("In dropdown_mode_cycleDensityPreviewStyle"); + densityPreviewStyle = index; + println("Style: " + densityPreviewStyle); } void numberbox_mode_changeDensityPreviewPosterize(int value) { @@ -760,20 +756,17 @@ void numberbox_mode_changePolygonizerLength(float value) { setupPolygonizer(); } - -void button_mode_cyclePolygonizer() -{ - - // this is a bit silly for only two choices - if (polygonizer == 1) { - polygonizer = 0; - } - else { - polygonizer++; - } +void numberbox_mode_changePolygonizerAdaptativeAngle(float value) { + println("numberbox_mode_changePolygonizerAdaptativeAngle"); + polygonizerAdaptativeAngle = value; + setupPolygonizer(); +} + + +void dropdown_mode_changePolygonizer(int value) +{ + polygonizer = value; setupPolygonizer(); - Controller c = cp5.getController(MODE_CHANGE_POLYGONIZER); - c.setLabel(this.controlLabels.get(MODE_CHANGE_POLYGONIZER) + ": " + polygonizer); } diff --git a/controlsSetup.pde b/controlsSetup.pde index ec7e22a..163a633 100644 --- a/controlsSetup.pde +++ b/controlsSetup.pde @@ -83,6 +83,14 @@ Set getControlsToLockIfImageNotLoaded() { return this.controlsToLockIfImageNotLoaded; } +Set getControlsToLockIfVectorNotLoaded() { + if (this.controlsToLockIfVectorNotLoaded == null) + { + this.controlsToLockIfVectorNotLoaded = buildControlsToLockIfVectorNotLoaded(); + } + return this.controlsToLockIfVectorNotLoaded; +} + void hideAllControls() { for (String key : allControls.keySet()) { @@ -227,9 +235,37 @@ Set buildControlsToLockIfImageNotLoaded() result.add(MODE_CHANGE_SAMPLE_AREA); result.add(MODE_SELECT_PICTUREFRAME); + result.add(MODE_CHANGE_PIXEL_SCALING); + result.add(MODE_CHOOSE_CHROMA_KEY_COLOUR); + return result; } +Set buildControlsToLockIfVectorNotLoaded() +{ + Set result = new HashSet(); + result.add(MODE_CHANGE_MIN_VECTOR_LINE_LENGTH); + result.add(MODE_RESIZE_VECTOR); + result.add(MODE_MOVE_VECTOR); + result.add(MODE_CHANGE_POLYGONIZER_LENGTH); + result.add(MODE_CHANGE_POLYGONIZER); + + return result; +} + +CallbackListener toFront = new CallbackListener() { + public void controlEvent(CallbackEvent theEvent) { + theEvent.getController().bringToFront(); + ((ScrollableList)theEvent.getController()).open(); + } +}; + +CallbackListener close = new CallbackListener() { + public void controlEvent(CallbackEvent theEvent) { + ((ScrollableList)theEvent.getController()).close(); + } +}; + Map buildAllControls() { @@ -243,6 +279,8 @@ Map buildAllControls() { Button b = cp5.addButton(controlName, 0, 100, 100, 100, 100); b.setLabel(getControlLabels().get(controlName)); + controlP5.Label l = b.getCaptionLabel(); + l.getStyle().marginLeft = 4; //move to the right b.hide(); map.put(controlName, b); // println("Added button " + controlName); @@ -283,11 +321,29 @@ Map buildAllControls() map.put(controlName, n); // println("Added numberbox " + controlName); } + else if (controlName.startsWith("dropdown_")) + { + ScrollableList sl = cp5.addScrollableList(controlName, 100, 100, 100, 100); + sl.setBarHeight(20); + sl.setItemHeight(20); + sl.setLabel(getControlLabels().get(controlName)); + sl.setType(ScrollableList.DROPDOWN); + sl.onEnter(toFront); + sl.onLeave(close); + sl.setHeight(100); + sl.hide(); + + controlP5.Label l = sl.getCaptionLabel(); + map.put(controlName, sl); + println("Added dropdown " + controlName); + } } initialiseButtonValues(map); initialiseToggleValues(map); initialiseNumberboxValues(map); + initialiseDropdownContents(map); + initialiseDropdownValues(map); return map; } @@ -298,12 +354,7 @@ Map initialiseButtonValues(Map map) if (key.startsWith("button_")) { Button n = (Button) map.get(key); - - if (MODE_CYCLE_DENSITY_PREVIEW_STYLE.equals(key)) { - n.setValue(densityPreviewStyle); - n.setLabel(this.controlLabels.get(MODE_CYCLE_DENSITY_PREVIEW_STYLE) + ": " + densityPreviewStyle); - } - else if (MODE_CHANGE_POLYGONIZER.equals(key)) { + if (MODE_CHANGE_POLYGONIZER.equals(key)) { n.setValue(polygonizer); n.setLabel(this.controlLabels.get(MODE_CHANGE_POLYGONIZER) + ": " + polygonizer); } @@ -536,8 +587,8 @@ Map initialiseNumberboxValues(Map map) } else if (MODE_ADJUST_PREVIEW_CORD_OFFSET.equals(key)) { - n.setDecimalPrecision(0); - n.setValue(0); + n.setDecimalPrecision(2); + n.setValue(0.0); n.setMultiplier(0.5); } else if (MODE_CHANGE_DENSITY_PREVIEW_POSTERIZE.equals(key)) @@ -553,7 +604,13 @@ Map initialiseNumberboxValues(Map map) n.setMin(1.0); n.setDecimalPrecision(1); n.setMultiplier(0.1); - + } + else if (MODE_CHANGE_POLYGONIZER_ADAPTATIVE_ANGLE.equals(key)) { + n.setValue(polygonizerAdaptativeAngle); + n.setMin(0.0); + n.setMax(1.57079632679); + n.setDecimalPrecision(2); + n.setMultiplier(0.01); } } } @@ -614,6 +671,51 @@ Map initialiseToggleValues(Map map) return map; } +Map initialiseDropdownContents(Map map) +{ + println("Init dropdown contents"); + for (String key : map.keySet()) + { + if (MODE_CYCLE_DENSITY_PREVIEW_STYLE.equals(key)) + { + println("Adding " + key); + ScrollableList sl = (ScrollableList) map.get(key); + sl.setItems(densityPreviewStyles); + } + if (MODE_CHANGE_POLYGONIZER.equals(key)) + { + println("Adding " + key); + ScrollableList sl = (ScrollableList) map.get(key); + sl.setItems(polygonizerStyles); + } + } + return map; +} + +Map initialiseDropdownValues(Map map) +{ + println("Init dropdown values"); + for (String key : map.keySet()) + { + if (MODE_CYCLE_DENSITY_PREVIEW_STYLE.equals(key)) + { + println("Adding " + key); + ScrollableList sl = (ScrollableList) map.get(key); + sl.setValue(densityPreviewStyle); + sl.close(); + } + else if (MODE_CHANGE_POLYGONIZER.equals(key)) + { + println("Adding " + key); + ScrollableList sl = (ScrollableList) map.get(key); + sl.setValue(polygonizer); + sl.close(); + } + } + return map; +} + + String getControlLabel(String butName) { if (controlLabels.containsKey(butName)) @@ -642,6 +744,18 @@ Map buildControlPositionsForPanel(Panel panel) col++; } } + else if (controller.getName().startsWith("dropdown_")) + { + PVector p = new PVector(col*(DEFAULT_CONTROL_SIZE.x+CONTROL_SPACING.x), row*(DEFAULT_CONTROL_SIZE.y+CONTROL_SPACING.y)); + println(controller); + map.put(controller.getName(), p); + row++; + if (p.y + (DEFAULT_CONTROL_SIZE.y*2) >= panel.getOutline().getHeight()) + { + row = 0; + col++; + } + } else { PVector p = new PVector(col*(DEFAULT_CONTROL_SIZE.x+CONTROL_SPACING.x), row*(DEFAULT_CONTROL_SIZE.y+CONTROL_SPACING.y)); @@ -671,6 +785,11 @@ Map buildControlSizesForPanel(Panel panel) PVector s = new PVector(DEFAULT_CONTROL_SIZE.y, DEFAULT_CONTROL_SIZE.y); map.put(controller.getName(), s); } + else if (controller.getName().startsWith("dropdown_")) + { + PVector s = new PVector(DEFAULT_CONTROL_SIZE.x, DEFAULT_CONTROL_SIZE.y * 4); + map.put(controller.getName(), s); + } else { PVector s = new PVector(DEFAULT_CONTROL_SIZE.x, DEFAULT_CONTROL_SIZE.y); @@ -732,8 +851,6 @@ List getControlNamesForInputPanel() controlNames.add(MODE_CHANGE_SAMPLE_AREA); controlNames.add(MODE_CHOOSE_CHROMA_KEY_COLOUR); controlNames.add(MODE_CHANGE_PIXEL_SCALING); - controlNames.add(MODE_CHANGE_DENSITY_PREVIEW_POSTERIZE); - controlNames.add(MODE_CYCLE_DENSITY_PREVIEW_STYLE); controlNames.add(MODE_RENDER_PIXEL_DIALOG); // controlNames.add(MODE_DRAW_GRID); @@ -744,20 +861,23 @@ List getControlNamesForInputPanel() controlNames.add(MODE_LOAD_VECTOR_FILE); controlNames.add(MODE_RESIZE_VECTOR); controlNames.add(MODE_MOVE_VECTOR); - controlNames.add(MODE_CHANGE_MIN_VECTOR_LINE_LENGTH); - //controlNames.add(MODE_VECTOR_PATH_LENGTH_HIGHPASS_CUTOFF); - controlNames.add(MODE_RENDER_VECTORS); - controlNames.add(MODE_ADJUST_PREVIEW_CORD_OFFSET); + + controlNames.add(MODE_RENDER_VECTORS); + controlNames.add(MODE_CHANGE_MIN_VECTOR_LINE_LENGTH); controlNames.add(MODE_CHANGE_POLYGONIZER); controlNames.add(MODE_CHANGE_POLYGONIZER_LENGTH); +// controlNames.add(MODE_CHANGE_POLYGONIZER_ADAPTATIVE_ANGLE); controlNames.add(MODE_SHOW_IMAGE); controlNames.add(MODE_SHOW_VECTOR); controlNames.add(MODE_SHOW_QUEUE_PREVIEW); - controlNames.add(MODE_SHOW_DENSITY_PREVIEW); controlNames.add(MODE_SHOW_GUIDES); + + controlNames.add(MODE_SHOW_DENSITY_PREVIEW); controlNames.add(MODE_PREVIEW_PIXEL_DENSITY_RANGE); + controlNames.add(MODE_CHANGE_DENSITY_PREVIEW_POSTERIZE); + controlNames.add(MODE_CYCLE_DENSITY_PREVIEW_STYLE); return controlNames; @@ -1025,13 +1145,14 @@ Map buildControlLabels() result.put(MODE_SEND_BUTTON_DEACTIVATE, "Deactivate button"); result.put(MODE_ADJUST_PREVIEW_CORD_OFFSET, "Cord offset"); - result.put(MODE_CYCLE_DENSITY_PREVIEW_STYLE, "Cycle preview style"); + result.put(MODE_CYCLE_DENSITY_PREVIEW_STYLE, "Density preview style"); result.put(MODE_CHANGE_DENSITY_PREVIEW_POSTERIZE, "Pixel posterize"); result.put(MODE_PREVIEW_PIXEL_DENSITY_RANGE, "Show density range"); - result.put(MODE_CHANGE_POLYGONIZER, "Cycle polygonizer"); + result.put(MODE_CHANGE_POLYGONIZER, "Polygonizer style"); result.put(MODE_CHANGE_POLYGONIZER_LENGTH, "Polygonizer length"); + result.put(MODE_CHANGE_POLYGONIZER_ADAPTATIVE_ANGLE, "Polygonizer angle"); return result; @@ -1184,6 +1305,7 @@ Set buildControlNames() result.add(MODE_CHANGE_POLYGONIZER_LENGTH); result.add(MODE_CHANGE_POLYGONIZER); + result.add(MODE_CHANGE_POLYGONIZER_ADAPTATIVE_ANGLE); return result; } diff --git a/drawing.pde b/drawing.pde index e7b7059..07197a3 100644 --- a/drawing.pde +++ b/drawing.pde @@ -912,12 +912,12 @@ List filterPointsLowPass(RPoint[] points, long filterParam, float scali { p = scaled.get(j); // and even then, only bother drawing if it's a move of over "x" steps - int diffx = int(p.x) - int(result.get(result.size()-1).x); - int diffy = int(p.y) - int(result.get(result.size()-1).y); + int diffx = abs(int(p.x) - int(result.get(result.size()-1).x)); + int diffy = abs(int(p.y) - int(result.get(result.size()-1).y)); - if (abs(diffx) > filterParam || abs(diffy) > filterParam) + if (diffx > filterParam || diffy > filterParam) { - println(j + ". Adding point " + p + ", last: " + result.get(result.size()-1)); + println(j + ". Adding point " + p + " because diffx (" + diffx + ") or diffy (" + diffy + ") is > " + filterParam + ", last: " + result.get(result.size()-1)); result.add(p); } } @@ -967,4 +967,4 @@ void sendStopSwirling() void sendDrawRandomSprite(String spriteFilename) { addToCommandQueue(CMD_DRAW_RANDOM_SPRITE+","+spriteFilename+",100,500,END"); -} \ No newline at end of file +} diff --git a/polargraphcontroller.pde b/polargraphcontroller.pde index 7c07e84..cc75a80 100644 --- a/polargraphcontroller.pde +++ b/polargraphcontroller.pde @@ -1,7 +1,7 @@ /** Polargraph controller - Copyright Sandy Noble 2015. - + Copyright Sandy Noble 2018. + This file is part of Polargraph Controller. Polargraph Controller is free software: you can redistribute it and/or modify @@ -35,11 +35,8 @@ import diewald_CV_kit.utility.*; import diewald_CV_kit.blobdetection.*; import geomerative.*; -//import org.apache.batik.svggen.font.table.*; -//import org.apache.batik.svggen.font.*; import java.util.zip.CRC32; - // for OSX import java.text.*; import java.util.*; @@ -58,7 +55,7 @@ import java.lang.reflect.Method; int majorVersionNo = 2; int minorVersionNo = 5; -int buildNo = 1; +int buildNo = 2; String programTitle = "Polargraph Controller v" + majorVersionNo + "." + minorVersionNo + " build " + buildNo; ControlP5 cp5; @@ -340,16 +337,16 @@ static final String MODE_SEND_BUTTON_DEACTIVATE = "button_mode_sendButtonDeactiv static final String MODE_ADJUST_PREVIEW_CORD_OFFSET = "numberbox_mode_previewCordOffsetValue"; -static final String MODE_CYCLE_DENSITY_PREVIEW_STYLE = "button_mode_cycleDensityPreviewStyle"; +static final String MODE_CYCLE_DENSITY_PREVIEW_STYLE = "dropdown_mode_cycleDensityPreviewStyle"; static final String MODE_CHANGE_DENSITY_PREVIEW_POSTERIZE = "numberbox_mode_changeDensityPreviewPosterize"; static final String MODE_PREVIEW_PIXEL_DENSITY_RANGE = "minitoggle_mode_previewPixelDensityRange"; -static final String MODE_CHANGE_POLYGONIZER = "button_mode_cyclePolygonizer"; +static final String MODE_CHANGE_POLYGONIZER = "dropdown_mode_changePolygonizer"; static final String MODE_CHANGE_POLYGONIZER_LENGTH = "numberbox_mode_changePolygonizerLength"; +static final String MODE_CHANGE_POLYGONIZER_ADAPTATIVE_ANGLE = "numberbox_mode_changePolygonizerAdaptativeAngle"; - - +List polygonizerStyles = Arrays.asList("ADAPTATIVE", "UNIFORMLENGTH"); PVector statusTextPosition = new PVector(300.0, 12.0); @@ -386,6 +383,8 @@ boolean displayingDensityPreview = false; boolean displayingGuides = true; + +List densityPreviewStyles = Arrays.asList("Round", "Diamond", "Native Simple", "Native Arc", "Round size", "Native size"); static final int DENSITY_PREVIEW_STYLE_COUNT = 6; static final int DENSITY_PREVIEW_ROUND = 0; @@ -496,6 +495,7 @@ float vectorScaling = 100; PVector vectorPosition = new PVector(0.0,0.0); int minimumVectorLineLength = 2; public static final int VECTOR_FILTER_LOW_PASS = 0; +public Set controlsToLockIfVectorNotLoaded = null; String storeFilename = "comm.txt"; @@ -544,6 +544,7 @@ boolean rescaleDisplayMachine = true; // Polygonization. It's a geomerative thing. int polygonizer = 0; float polygonizerLength = 0.0; +float polygonizerAdaptativeAngle = 0.0F; void setup() { @@ -1081,411 +1082,6 @@ void showGroupBox() } -void loadImageWithFileChooser() -{ - SwingUtilities.invokeLater(new Runnable() - { - public void run() { - JFileChooser fc = new JFileChooser(); - if (lastImageDirectory != null) fc.setCurrentDirectory(lastImageDirectory); - fc.setFileFilter(new ImageFileFilter()); - fc.setDialogTitle("Choose an image file..."); - - int returned = fc.showOpenDialog(frame); - - lastImageDirectory = fc.getCurrentDirectory(); - - if (returned == JFileChooser.APPROVE_OPTION) - { - File file = fc.getSelectedFile(); - // see if it's an image - PImage img = loadImage(file.getPath()); - if (img != null) - { - img = null; - getDisplayMachine().loadNewImageFromFilename(file.getPath()); - if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) - { - getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); - } - } - } - } - }); -} - -class ImageFileFilter extends javax.swing.filechooser.FileFilter -{ - public boolean accept(File file) { - String filename = file.getName(); - filename.toLowerCase(); - if (file.isDirectory() || filename.endsWith(".png") || filename.endsWith(".jpg") || filename.endsWith(".jpeg")) - return true; - else - return false; - } - public String getDescription() { - return "Image files (PNG or JPG)"; - } -} - -void loadVectorWithFileChooser() -{ - SwingUtilities.invokeLater(new Runnable() - { - public void run() { - JFileChooser fc = new JFileChooser(); - if (lastImageDirectory != null) - { - fc.setCurrentDirectory(lastImageDirectory); - } - - fc.setFileFilter(new VectorFileFilter()); - fc.setDialogTitle("Choose a vector file..."); - int returned = fc.showOpenDialog(frame); - lastImageDirectory = fc.getCurrentDirectory(); - - if (returned == JFileChooser.APPROVE_OPTION) - { - File file = fc.getSelectedFile(); - if (file.exists()) - { - RShape shape = loadShapeFromFile(file.getPath()); - if (shape != null) - { - setVectorFilename(file.getPath()); - setVectorShape(shape); - } - else - { - println("File not found (" + file.getPath() + ")"); - } - } - } - } - } - ); -} - -class VectorFileFilter extends javax.swing.filechooser.FileFilter -{ - public boolean accept(File file) { - String filename = file.getName(); - filename.toLowerCase(); - if (file.isDirectory() || filename.endsWith(".svg") || isGCodeExtension(filename)) - return true; - else - return false; - } - public String getDescription() { - return "Vector graphic files (SVG, GCode)"; - } -} - -void loadNewPropertiesFilenameWithFileChooser() -{ - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - JFileChooser fc = new JFileChooser(); - if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); - fc.setFileFilter(new PropertiesFileFilter()); - - fc.setDialogTitle("Choose a config file..."); - - int returned = fc.showOpenDialog(frame); - - lastPropertiesDirectory = fc.getCurrentDirectory(); - - if (returned == JFileChooser.APPROVE_OPTION) - { - File file = fc.getSelectedFile(); - if (file.exists()) - { - println("New properties file exists."); - newPropertiesFilename = file.toString(); - println("new propertiesFilename: "+ newPropertiesFilename); - propertiesFilename = newPropertiesFilename; - // clear old properties. - props = null; - loadFromPropertiesFile(); - - // set values of number spinners etc - updateNumberboxValues(); - } - } - } - }); -} - -class PropertiesFileFilter extends javax.swing.filechooser.FileFilter -{ - public boolean accept(File file) { - String filename = file.getName(); - filename.toLowerCase(); - if (file.isDirectory() || filename.endsWith(".properties.txt")) - return true; - else - return false; - } - public String getDescription() { - return "Properties files (*.properties.txt)"; - } -} - -void saveNewPropertiesFileWithFileChooser() -{ - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - JFileChooser fc = new JFileChooser(); - if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); - fc.setFileFilter(new PropertiesFileFilter()); - - fc.setDialogTitle("Enter a config file name..."); - - int returned = fc.showSaveDialog(frame); - if (returned == JFileChooser.APPROVE_OPTION) - { - File file = fc.getSelectedFile(); - newPropertiesFilename = file.toString(); - newPropertiesFilename.toLowerCase(); - if (!newPropertiesFilename.endsWith(".properties.txt")) - newPropertiesFilename+=".properties.txt"; - - println("new propertiesFilename: "+ newPropertiesFilename); - propertiesFilename = newPropertiesFilename; - savePropertiesFile(); - // clear old properties. - props = null; - loadFromPropertiesFile(); - } - } - }); -} - - - -RShape loadShapeFromFile(String filename) { - RShape sh = null; - if (filename.toLowerCase().endsWith(".svg")) { - sh = RG.loadShape(filename); - } - else if (isGCodeExtension(filename)) { - sh = loadShapeFromGCodeFile(filename); - } - return sh; -} - - -boolean isGCodeExtension(String filename) { - return (filename.toLowerCase().endsWith(".gcode") || filename.toLowerCase().endsWith(".g") || filename.toLowerCase().endsWith(".ngc") || filename.toLowerCase().endsWith(".txt")); -} - - -int countLines(String filename) throws IOException { - InputStream is = new BufferedInputStream(new FileInputStream(filename)); - try { - byte[] c = new byte[1024]; - int count = 0; - int readChars = 0; - boolean empty = true; - while ((readChars = is.read(c)) != -1) { - empty = false; - for (int i = 0; i < readChars; ++i) { - if (c[i] == '\n') { - ++count; - } - } - } - return (count == 0 && !empty) ? 1 : count+1; - } finally { - is.close(); - } -} - -RShape loadShapeFromGCodeFile(String filename) { - noLoop(); - RShape parent = null; - BufferedReader reader = null; - long totalPoints = 0; - long time = millis(); - long countLines = 0; - - try { - countLines = countLines(filename); - println("" + countLines + " lines found."); - if (countLines < 1) { - throw new IOException("No lines found in GCode file."); - } - reader = createReader(filename); - parent = new RShape(); - String line; - boolean drawLine = false; - int gCodeZAxisChanges = 0; - - long lineNo = 0; - float lastPercent = 0.0f; - boolean reportStatus = true; - while ((line = reader.readLine ()) != null) { - lineNo++; -// println("Line: " + line); - - if (reportStatus) { - float percent = ((float)lineNo / (float)countLines) * 100.0; - println("----" + percent + "% of the way through."); - lastPercent = percent; - } - - if (line.toUpperCase().startsWith("G")) { - if (reportStatus) { - println(new StringBuilder().append(lineNo).append(" of ").append(countLines).append(": ").append(line).append(". Points: ").append(totalPoints).toString()); - long free = Runtime.getRuntime().freeMemory(); - long maximum = Runtime.getRuntime().maxMemory(); - println(new StringBuilder().append("Free: ").append(free).append(", max: ").append(maximum).toString()); - } - - Map ins = null; - try { - ins = unpackGCodeInstruction(line); - } - catch (Exception e) { - println(e.toString()); - continue; - } -// println("Ins: " + ins); - Integer code = Math.round(ins.get("G")); - - Float z = ins.get("Z"); - if (z != null) { - gCodeZAxisChanges++; - if (gCodeZAxisChanges == 2) { - println("Assume second z axis change is to drop the pen to start drawing " + z); - gcodeZAxisDrawingHeight = z; - drawLine = true; - } - else if (gCodeZAxisChanges > 2) { - drawLine = isGCodeZAxisForDrawing(z); - } - else { - println("Assume first z axis change is to RAISE the pen " + z); - drawLine = false; - } - } - - Float x = ins.get("X"); - Float y = ins.get("Y"); - if (x != null && y == null) { - // move x axis only, use y of last - RPoint[][] points = parent.getPointsInPaths(); - RPoint rp = points[points.length-1][points[points.length-1].length-1]; - y = rp.y; - } - else if (x == null && y != null) { - // move y axis only, use x of last - RPoint[][] points = parent.getPointsInPaths(); - RPoint rp = points[points.length-1][points[points.length-1].length-1]; - x = rp.x; - } - - if (x != null && y != null) { - // move both x and y axis - if (drawLine) { - parent.addLineTo(x, y); - } - else { - parent.addMoveTo(x, y); - } - } - } - else { - - } - - if ((millis() - time) > 500) { - time = millis(); - reportStatus = true; - } - else { - reportStatus = false; - } - - if (lineNo == (countLines-1)) { - reportStatus = true; - } - - } - } - catch (IOException e) { - println("IOExecption reading lines from the gcode file " + filename); - e.printStackTrace(); - } - finally { - try { - reader.close(); - } - catch (IOException e) { - println("IOException closing the gcode file " + filename); - e.printStackTrace(); - } - } - - RPoint[][] points = parent.getPointsInPaths(); - totalPoints = 0; - if (points != null) { - for (int i = 0; i unpackGCodeInstruction(String line) throws Exception { - Map instruction = new HashMap(4); - try { - String[] splitted = line.trim().split(" "); - for (int i = 0; i < splitted.length; i++) { - // remove ; character - splitted[i] = splitted[i].replace(";", ""); - String axis = splitted[i].substring(0, 1); - String sanitisedValue = splitted[i].substring(1); - sanitisedValue = sanitisedValue.replace(",", "."); - Float value = Float.parseFloat(sanitisedValue); - if ("X".equalsIgnoreCase(axis) || "Y".equalsIgnoreCase(axis) || "Z".equalsIgnoreCase(axis) || "G".equalsIgnoreCase(axis)) { - instruction.put(axis.toUpperCase(), value); - } - } -// println("instruction: " + instruction); - if (instruction.isEmpty()) { - throw new Exception("Empty instruction"); - } - } - catch (NumberFormatException nfe) { - println("Number format exception: " + nfe.getMessage()); - } - catch (Exception e) { - println("e: " + e); - throw new Exception("Exception while reading the lines from a gcode file: " + line + ", " + e.getMessage()); - } - - return instruction; -} void setPictureFrameDimensionsToBox() @@ -1799,7 +1395,7 @@ void mouseClicked() if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); } - else if (currentMode.equals(MODE_MOVE_VECTOR)) + else if (currentMode.equals(MODE_MOVE_VECTOR) && vectorShape != null) { // offset mouse vector so it grabs the centre of the shape PVector centroid = new PVector(getVectorShape().width/2, getVectorShape().height/2); @@ -1934,23 +1530,23 @@ void leftButtonMachineClick() void mouseWheel(int delta) { noLoop(); - // get the mouse position on the machine, before changing the machine scaling - PVector pos = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); - changeMachineScaling(delta); - // now work out what the machine position needs to be to line the pos up with mousevector again - PVector scaledPos = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); -// println("original pos: " + pos); -// println("scaled pos: " + scaledPos); + if (mouseOverMachine()) { + // get the mouse position on the machine, before changing the machine scaling + PVector pos = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); + changeMachineScaling(delta); + + // now work out what the machine position needs to be to line the pos up with mousevector again + PVector scaledPos = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); + PVector change = PVector.sub(scaledPos, pos); - PVector change = PVector.sub(scaledPos, pos); -// println("change: " + change); - - // and adjust for the new scaling factor - change.mult(machineScaling); + // and adjust for the new scaling factor + change.mult(machineScaling); + + // finally update the machine offset (position) + getDisplayMachine().getOffset().add(change); + } - // finally update the machine offset (position) - getDisplayMachine().getOffset().add(change); loop(); } @@ -3082,6 +2678,9 @@ void loadFromPropertiesFile() // println("home point loaded: " + homePointCartesian + ", " + getHomePoint()); // Geomerative stuff + println("RG.ADAPTATIVE is " + RG.ADAPTATIVE); + println("RG.UNIFORMLENGTH is " + RG.UNIFORMLENGTH); + println("RG.UNIFORMSTEP is " + RG.UNIFORMSTEP); polygonizer = getIntProperty("controller.geomerative.polygonizer", RG.ADAPTATIVE); polygonizerLength = getFloatProperty("controller.geomerative.polygonizerLength", 1.0); setupPolygonizer(); @@ -3342,12 +2941,15 @@ Integer getBaudRate() void setupPolygonizer() { RG.setPolygonizer(polygonizer); // http://www.polargraph.co.uk/forum/polargraphs-group2/troubleshooting-forum5/svg-differences-between-polargraphcontroller-2-1-1-2-3-0-thread523.0 switch(polygonizer) { - case 1: RG.setPolygonizerLength(polygonizerLength); break; - } - println("Polygonizer: " + polygonizer); - println("PolygonizerLength: " + polygonizerLength); - +// case 0: +// RG.setPolygonizerAngle(polygonizerAdaptativeAngle); +// break; + case 1: + RG.setPolygonizerLength(polygonizerLength); + break; + } } + void initLogging() { try diff --git a/queue.pde b/queue.pde new file mode 100644 index 0000000..9bfcf6b --- /dev/null +++ b/queue.pde @@ -0,0 +1,7 @@ +/** +Tools for dealing with a full command queue. + +Optimise queue. +*/ + +