├── .gitignore ├── BLOBables.pde ├── ControlFrame.pde ├── ControlFrameSimple.pde ├── DisplayMachine.pde ├── DrawPixelsWindow.pde ├── FileLoading.pde ├── Machine.pde ├── MachineExecWindow.pde ├── MachineStoreWindow.pde ├── Misc.pde ├── Panel.pde ├── Polargraphsd spec.properties.txt ├── README.md ├── Rectangle.pde ├── SerialPortWindow.pde ├── controlsActions.pde ├── controlsActionsWindows.pde ├── controlsSetup.pde ├── data ├── a.png ├── b.png ├── dpadlr.png ├── dpadud.png ├── gradient.jpg ├── gradient.pbm ├── highrect.svg ├── midsquare.svg ├── midsquares.svg ├── x.png └── y.png ├── drawing.pde ├── gpl.txt ├── ikea.properties.txt ├── polargraphcontroller.pde ├── queue.pde ├── tabSetup.pde └── trace.pde /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /default.properties.txt 3 | /application.windows32 4 | /application.windows64 5 | /application.linux32 6 | /application.linux64 7 | /application.macosx 8 | Thumbs.db 9 | xx_default.properties.txt 10 | -------------------------------------------------------------------------------- /BLOBables.pde: -------------------------------------------------------------------------------- 1 | public final class BLOBable_blueBlobs implements BLOBable{ 2 | private PImage img_; 3 | int col = color(0, 0, 255); 4 | 5 | public BLOBable_blueBlobs(PImage img){ 6 | img_ = img; 7 | } 8 | 9 | //@Override 10 | public final void init() { 11 | } 12 | 13 | //@Override 14 | public final void updateOnFrame(int width, int height) { 15 | } 16 | //@Override 17 | public final boolean isBLOBable(int pixel_index, int x, int y) { 18 | if( img_.pixels[pixel_index] == col){ 19 | return true; 20 | } else { 21 | return false; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ControlFrame.pde: -------------------------------------------------------------------------------- 1 | // the ControlFrame class extends PApplet, so we 2 | // are creating a new processing applet inside a 3 | // new frame with a controlP5 object loaded 4 | public class ControlFrame extends PApplet { 5 | public int w, h; 6 | int abc = 100; 7 | public ControlP5 cp5; 8 | protected PApplet parent; 9 | 10 | private ControlFrame() { 11 | } 12 | 13 | public ControlFrame(PApplet theParent, int theWidth, int theHeight) { 14 | this.parent = theParent; 15 | this.w = theWidth; 16 | this.h = theHeight; 17 | } 18 | 19 | public ControlP5 cp5() { 20 | if (this.cp5 == null) { 21 | this.cp5 = this.setupControlP5(); 22 | } 23 | return this.cp5; 24 | } 25 | 26 | public PApplet getParent() { 27 | return this.parent; 28 | } 29 | 30 | public void setup() { 31 | size(w, h); 32 | frameRate(5); 33 | } 34 | 35 | public ControlP5 setupControlP5() { 36 | println("About to create new ControlP5"); 37 | ControlP5 cp5 = new ControlP5(this); 38 | println("Created: " + cp5); 39 | while (cp5 == null) { 40 | println("Was null: " + cp5); 41 | } 42 | println("Finally created: " + cp5); 43 | return cp5; 44 | } 45 | 46 | public void draw() { 47 | background(abc); 48 | } 49 | } 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ControlFrameSimple.pde: -------------------------------------------------------------------------------- 1 | 2 | // the ControlFrame class extends PApplet, so we 3 | // are creating a new processing applet inside a 4 | // new frame with a controlP5 object loaded 5 | public class ControlFrameSimple extends PApplet { 6 | 7 | int w, h; 8 | 9 | int bg; 10 | 11 | public void setup() { 12 | size(w, h); 13 | frameRate(5); 14 | cp5 = new ControlP5( this ); 15 | } 16 | 17 | public void draw() { 18 | background( bg ); 19 | } 20 | 21 | private ControlFrameSimple() { 22 | } 23 | 24 | public ControlFrameSimple(Object theParent, int theWidth, int theHeight, int theColor) { 25 | parent = theParent; 26 | w = theWidth; 27 | h = theHeight; 28 | bg = theColor; 29 | } 30 | 31 | 32 | public ControlP5 cp5() { 33 | return this.cp5; 34 | } 35 | 36 | ControlP5 cp5; 37 | 38 | Object parent; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /DisplayMachine.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | 30 | class DisplayMachine extends Machine 31 | { 32 | private Rectangle outline = null; 33 | private float scaling = 1.0; 34 | private Scaler scaler = null; 35 | private PVector offset = null; 36 | private float imageTransparency = 1.0; 37 | 38 | private Set extractedPixels = new HashSet(0); 39 | 40 | PImage scaledImage = null; 41 | 42 | private PVector currentPixel = null; 43 | 44 | public DisplayMachine(Machine m, PVector offset, float scaling) 45 | { 46 | // construct 47 | super(m.getWidth(), m.getHeight(), m.getMMPerRev(), m.getStepsPerRev()); 48 | 49 | super.machineSize = m.machineSize; 50 | 51 | super.page = m.page; 52 | super.imageFrame = m.imageFrame; 53 | super.pictureFrame = m.pictureFrame; 54 | 55 | super.imageBitmap = m.imageBitmap; 56 | super.imageFilename = m.imageFilename; 57 | 58 | super.stepsPerRev = m.stepsPerRev; 59 | super.mmPerRev = m.mmPerRev; 60 | 61 | super.mmPerStep = m.mmPerStep; 62 | super.stepsPerMM = m.stepsPerMM; 63 | super.maxLength = m.maxLength; 64 | super.gridSize = m.gridSize; 65 | 66 | this.offset = offset; 67 | this.scaling = scaling; 68 | this.scaler = new Scaler(scaling, 100.0); 69 | 70 | this.outline = null; 71 | } 72 | 73 | public Rectangle getOutline() 74 | { 75 | outline = new Rectangle(offset, new PVector(sc(super.getWidth()), sc(super.getHeight()))); 76 | return this.outline; 77 | } 78 | 79 | private Scaler getScaler() 80 | { 81 | if (scaler == null) 82 | this.scaler = new Scaler(getScaling(), getMMPerStep()); 83 | return scaler; 84 | } 85 | 86 | public void setScale(float scale) 87 | { 88 | this.scaling = scale; 89 | this.scaler = new Scaler(scale, getMMPerStep()); 90 | } 91 | public float getScaling() 92 | { 93 | return this.scaling; 94 | } 95 | public float sc(float val) 96 | { 97 | return getScaler().scale(val); 98 | } 99 | public void setOffset(PVector offset) 100 | { 101 | this.offset = offset; 102 | } 103 | public PVector getOffset() 104 | { 105 | return this.offset; 106 | } 107 | public void setImageTransparency(float trans) 108 | { 109 | this.imageTransparency = trans; 110 | } 111 | public int getImageTransparency() 112 | { 113 | float f = 255.0 * this.imageTransparency; 114 | f += 0.5; 115 | int result = (int) f; 116 | return result; 117 | } 118 | 119 | public PVector getCurrentPixel() 120 | { 121 | return this.currentPixel; 122 | } 123 | public void setCurrentPixel(PVector p) 124 | { 125 | this.currentPixel = p; 126 | } 127 | 128 | public void loadNewImageFromFilename(String filename) 129 | { 130 | super.loadImageFromFilename(filename); 131 | super.sizeImageFrameToImageAspectRatio(); 132 | this.setExtractedPixels(new HashSet(0)); 133 | } 134 | 135 | public final int DROP_SHADOW_DISTANCE = 4; 136 | public String getZoomText() 137 | { 138 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 139 | DecimalFormat df = (DecimalFormat)nf; 140 | df.applyPattern("###"); 141 | String zoom = df.format(scaling * 100) + "% zoom"; 142 | return zoom; 143 | } 144 | 145 | public String getDimensionsAsText(Rectangle r) 146 | { 147 | return getDimensionsAsText(r.getSize()); 148 | } 149 | public String getDimensionsAsText(PVector p) 150 | { 151 | String dim = inMM(p.x) + " x " + inMM(p.y) + "mm"; 152 | return dim; 153 | } 154 | 155 | public void drawForSetup() 156 | { 157 | // work out the scaling factor. 158 | noStroke(); 159 | // draw machine outline 160 | 161 | // drop shadow 162 | fill(80); 163 | rect(getOutline().getLeft()+DROP_SHADOW_DISTANCE, getOutline().getTop()+DROP_SHADOW_DISTANCE, getOutline().getWidth(), getOutline().getHeight()); 164 | 165 | fill(getMachineColour()); 166 | rect(getOutline().getLeft(), getOutline().getTop(), getOutline().getWidth(), getOutline().getHeight()); 167 | text("machine " + getDimensionsAsText(getSize()) + " " + getZoomText(), getOutline().getLeft(), getOutline().getTop()); 168 | 169 | if (displayingGuides) 170 | { 171 | // draw some guides 172 | stroke(getGuideColour()); 173 | strokeWeight(1); 174 | // centre line 175 | line(getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getTop(), 176 | getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getBottom()); 177 | 178 | // page top line 179 | line(getOutline().getLeft(), getOutline().getTop()+sc(getHomePoint().y), 180 | getOutline().getRight(), getOutline().getTop()+sc(getHomePoint().y)); 181 | } 182 | 183 | // draw page 184 | fill(getPageColour()); 185 | rect(getOutline().getLeft()+sc(getPage().getLeft()), 186 | getOutline().getTop()+sc(getPage().getTop()), 187 | sc(getPage().getWidth()), 188 | sc(getPage().getHeight())); 189 | text("page " + getDimensionsAsText(getPage()), getOutline().getLeft()+sc(getPage().getLeft()), 190 | getOutline().getTop()+sc(getPage().getTop())); 191 | fill(0); 192 | text("offset " + getDimensionsAsText(getPage().getPosition()), 193 | getOutline().getLeft()+sc(getPage().getLeft()), 194 | getOutline().getTop()+sc(getPage().getTop())+10); 195 | noFill(); 196 | 197 | // draw home point 198 | noFill(); 199 | strokeWeight(5); 200 | stroke(0, 128); 201 | PVector onScreen = scaleToScreen(inMM(getHomePoint())); 202 | ellipse(onScreen.x, onScreen.y, 15, 15); 203 | strokeWeight(2); 204 | stroke(255); 205 | ellipse(onScreen.x, onScreen.y, 15, 15); 206 | 207 | text("Home point", onScreen.x+ 15, onScreen.y-5); 208 | text(int(inMM(getHomePoint().x)+0.5) + ", " + int(inMM(getHomePoint().y)+0.5), onScreen.x+ 15, onScreen.y+15); 209 | 210 | 211 | if (displayingGuides 212 | && getOutline().surrounds(getMouseVector()) 213 | && currentMode != MODE_MOVE_IMAGE 214 | && mouseOverControls().isEmpty() 215 | ) 216 | { 217 | drawHangingStrings(); 218 | drawLineLengthTexts(); 219 | cursor(CROSS); 220 | } 221 | else 222 | { 223 | cursor(ARROW); 224 | } 225 | } 226 | 227 | public void drawLineLengthTexts() 228 | { 229 | PVector actual = inMM(asNativeCoords(inSteps(scaleToDisplayMachine(getMouseVector())))); 230 | PVector cart = scaleToDisplayMachine(getMouseVector()); 231 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 232 | DecimalFormat df = (DecimalFormat)nf; 233 | df.applyPattern("###.#"); 234 | 235 | text("Line 1: " + df.format(actual.x) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+18); 236 | text("Line 2: " + df.format(actual.y) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+28); 237 | 238 | text("X Position: " + df.format(cart.x) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+42); 239 | text("Y Position: " + df.format(cart.y) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+52); 240 | } 241 | 242 | public void draw() 243 | { 244 | // work out the scaling factor. 245 | noStroke(); 246 | // draw machine outline 247 | 248 | // fill(80); 249 | // rect(getOutline().getLeft()+DROP_SHADOW_DISTANCE, getOutline().getTop()+DROP_SHADOW_DISTANCE, getOutline().getWidth(), getOutline().getHeight()); 250 | 251 | fill(getMachineColour()); 252 | rect(getOutline().getLeft(), getOutline().getTop(), getOutline().getWidth(), getOutline().getHeight()); 253 | 254 | 255 | 256 | if (displayingGuides) 257 | { 258 | // draw some guides 259 | stroke(getGuideColour()); 260 | strokeWeight(1); 261 | // centre line 262 | line(getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getTop(), 263 | getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getBottom()); 264 | 265 | // page top line 266 | line(getOutline().getLeft(), getOutline().getTop()+sc(getHomePoint().y), 267 | getOutline().getRight(), getOutline().getTop()+sc(getHomePoint().y)); 268 | } 269 | 270 | // draw page 271 | fill(getPageColour()); 272 | rect(getOutline().getLeft()+sc(getPage().getLeft()), 273 | getOutline().getTop()+sc(getPage().getTop()), 274 | sc(getPage().getWidth()), 275 | sc(getPage().getHeight())); 276 | text("page " + getDimensionsAsText(getPage()), getOutline().getLeft()+sc(getPage().getLeft()), 277 | getOutline().getTop()+sc(getPage().getTop())-3); 278 | noFill(); 279 | 280 | 281 | 282 | // draw actual image 283 | if (displayingImage && imageIsReady()) 284 | { 285 | float ox = getOutline().getLeft()+sc(getImageFrame().getLeft()); 286 | float oy = getOutline().getTop()+sc(getImageFrame().getTop()); 287 | float w = sc(getImageFrame().getWidth()); 288 | float h = sc(getImageFrame().getHeight()); 289 | tint(255, getImageTransparency()); 290 | image(getImage(), ox, oy, w, h); 291 | noTint(); 292 | strokeWeight(1); 293 | stroke(150, 150, 150, 40); 294 | rect(ox, oy, w-1, h-1); 295 | fill(150, 150, 150, 40); 296 | text("image", ox, oy-3); 297 | noFill(); 298 | } 299 | 300 | stroke(getBackgroundColour(),150); 301 | strokeWeight(3); 302 | noFill(); 303 | rect(getOutline().getLeft()-2, getOutline().getTop()-2, getOutline().getWidth()+3, getOutline().getHeight()+3); 304 | 305 | stroke(getMachineColour(),150); 306 | strokeWeight(3); 307 | noFill(); 308 | rect(getOutline().getLeft()+sc(getPage().getLeft())-2, 309 | getOutline().getTop()+sc(getPage().getTop())-2, 310 | sc(getPage().getWidth())+4, 311 | sc(getPage().getHeight())+4); 312 | 313 | 314 | 315 | if (displayingSelectedCentres) 316 | { 317 | drawExtractedPixelCentres(); 318 | } 319 | if (displayingGridSpots) 320 | { 321 | drawGridIntersections(); 322 | } 323 | if (displayingDensityPreview) 324 | { 325 | drawExtractedPixelDensities(); 326 | } 327 | if (displayingGuides) 328 | { 329 | drawPictureFrame(); 330 | } 331 | 332 | if (displayingVector && getVectorShape() != null) 333 | { 334 | displayVectorImage(); 335 | } 336 | 337 | if (displayingGuides 338 | && getOutline().surrounds(getMouseVector()) 339 | && currentMode != MODE_MOVE_IMAGE 340 | && mouseOverControls().isEmpty() 341 | ) 342 | { 343 | drawHangingStrings(); 344 | drawRows(); 345 | cursor(CROSS); 346 | } 347 | else 348 | { 349 | cursor(ARROW); 350 | } 351 | } 352 | 353 | public void drawForTrace() 354 | { 355 | // work out the scaling factor. 356 | noStroke(); 357 | // draw machine outline 358 | 359 | // liveImage = trace_buildLiveImage(); 360 | // draw actual image 361 | 362 | // if (drawingLiveVideo) 363 | // { 364 | // displayLiveVideo(); 365 | // } 366 | 367 | if (drawingTraceShape && traceShape != null) 368 | { 369 | displaytraceShape(); 370 | } 371 | else 372 | { 373 | 374 | } 375 | } 376 | 377 | // public void displayLiveVideo() 378 | // { 379 | // // draw actual image, maximum size 380 | // if (processedLiveImage != null) 381 | // { 382 | // // origin - top left of the corner 383 | // float ox = getPanel(PANEL_NAME_WEBCAM).getOutline().getRight()+7; 384 | // float oy = getPanel(PANEL_NAME_GENERAL).getOutline().getTop(); 385 | // 386 | // // calculate size to display at. 387 | // float aspectRatio = (rotateWebcamImage) ? 480.0/640.0 : 640.0/480.0; // rotated, remember 388 | // float h = height - getPanel(PANEL_NAME_GENERAL).getOutline().getTop() -10; 389 | // float w = h * (480.0/640.0); 390 | //// println("height: " + h + ", width: " + w); 391 | //// println("origin x: " + ox + ", y: " + oy); 392 | // 393 | // if (rotateWebcamImage) 394 | // { 395 | // float t = h; 396 | // h = w; 397 | // w = t; 398 | // } 399 | // 400 | // //stroke(255); 401 | // rect(ox,oy,w,h); 402 | // 403 | // tint(255, getImageTransparency()); 404 | // if (rotateWebcamImage) 405 | // { 406 | // translate(ox, oy); 407 | // rotate(radians(270)); 408 | // image(processedLiveImage, -w, 0, w, h); 409 | // image(liveImage, -w, (w-(w/4))+10, w/4, h/4); 410 | //// stroke(0,255,0); 411 | //// ellipse(0,0,80,40); 412 | //// stroke(0,0,255); 413 | //// ellipse(-w,0,80,40); 414 | // rotate(radians(-270)); 415 | // translate(-ox, -oy); 416 | // } 417 | // else 418 | // { 419 | // translate(ox, oy); 420 | // image(processedLiveImage, 0, 0, h, w); 421 | // image(liveImage, h-(h/4), w+10, h/4, w/4); 422 | // translate(-ox, -oy); 423 | // } 424 | // noTint(); 425 | // noFill(); 426 | // } 427 | // } 428 | 429 | public void displaytraceShape() 430 | { 431 | strokeWeight(1); 432 | 433 | if (captureShape != null) 434 | { 435 | //displaytraceShapeAtFullSize(traceShape, false, color(150,150,150)); 436 | displaytraceShapeAtFullSize(captureShape, true, color(0,0,0)); 437 | } 438 | else 439 | { 440 | displaytraceShapeAtFullSize(traceShape, false, color(255,255,255)); 441 | } 442 | } 443 | 444 | public void displaytraceShapeAtFullSize(RShape vec, boolean illustrateSequence, Integer colour) 445 | { 446 | RG.ignoreStyles(); 447 | // work out scaling to make it full size on the screen 448 | float aspectRatio = vec.getWidth()/vec.getHeight(); // rotated, remember 449 | float h = height - getPanel(PANEL_NAME_GENERAL).getOutline().getTop() -10; 450 | float w = h * aspectRatio; 451 | float scaler = h / vec.getWidth(); 452 | if (rotateWebcamImage) 453 | scaler = h / vec.getHeight(); 454 | PVector position = new PVector(getPanel(PANEL_NAME_TRACE).getOutline().getRight()+7, getPanel(PANEL_NAME_GENERAL).getOutline().getTop()); 455 | 456 | noFill(); 457 | RPoint[][] pointPaths = vec.getPointsInPaths(); 458 | if (illustrateSequence) 459 | pointPaths = sortPathsCentreFirst(vec, pathLengthHighPassCutoff); 460 | 461 | if (pointPaths != null) 462 | { 463 | float incPerPath = 0.0; 464 | if (illustrateSequence) 465 | incPerPath = 255.0 / (float) pointPaths.length; 466 | 467 | for(int i = 0; i= pathLengthHighPassCutoff) 471 | // { 472 | if (pointPaths[i] != null) 473 | { 474 | if (illustrateSequence) 475 | stroke((int)col, (int)col, (int)col, 128); 476 | else 477 | stroke(colour); 478 | 479 | beginShape(); 480 | for (int j = 0; j= 2.0) { 730 | maxDens = int(numberOfSegments); 731 | } 732 | 733 | if (maxDens <= 1) { 734 | maxDens = 1; 735 | } 736 | 737 | return maxDens; 738 | } 739 | 740 | void drawExtractedPixelDensities() 741 | { 742 | 743 | float pixelSize = inMM(getGridSize()) * getScaling(); 744 | pixelSize = (pixelSize < 1.0) ? 1.0 : pixelSize; 745 | 746 | pixelSize = pixelSize * getPixelScalingOverGridSize(); 747 | 748 | float rowSizeInMM = inMM(getGridSize()) * getPixelScalingOverGridSize(); 749 | 750 | int posterizeLevels = 255; 751 | 752 | if (previewPixelDensityRange) { 753 | posterizeLevels = pixel_maxDensity(currentPenWidth, rowSizeInMM); 754 | } 755 | else { 756 | posterizeLevels = densityPreviewPosterize; 757 | } 758 | 759 | if (getExtractedPixels() != null) 760 | { 761 | for (PVector cartesianPos : getExtractedPixels()) 762 | { 763 | if ((cartesianPos.z <= pixelExtractBrightThreshold) && 764 | (cartesianPos.z >= pixelExtractDarkThreshold)) 765 | { 766 | // scale em, danno. 767 | PVector scaledPos = scaleToScreen(cartesianPos); 768 | noStroke(); 769 | if ((scaledPos.x <= 0) || (scaledPos.x > windowWidth) || 770 | (scaledPos.y <= 0) || (scaledPos.y > windowHeight)) { 771 | continue; 772 | } 773 | 774 | // Posterize the density value 775 | int reduced = int(map(cartesianPos.z, 1, 255, 1, posterizeLevels)+0.5); 776 | int brightness = int(map(reduced, 1, posterizeLevels, 1, 255)); 777 | 778 | fill(brightness); 779 | switch (getDensityPreviewStyle()) 780 | { 781 | case DENSITY_PREVIEW_ROUND: 782 | previewRoundPixel(scaledPos, pixelSize); 783 | break; 784 | case DENSITY_PREVIEW_ROUND_SIZE: 785 | fill(0); 786 | previewRoundPixel(scaledPos, map(brightness, 1, posterizeLevels, pixelSize, 1)); 787 | break; 788 | case DENSITY_PREVIEW_DIAMOND: 789 | previewDiamondPixel(scaledPos, pixelSize, pixelSize, brightness); 790 | break; 791 | case DENSITY_PREVIEW_NATIVE: 792 | previewNativePixel(scaledPos, pixelSize, brightness); 793 | break; 794 | case DENSITY_PREVIEW_NATIVE_SIZE: 795 | previewNativePixel(scaledPos, map(brightness, 1, posterizeLevels, pixelSize, 1), 50); 796 | break; 797 | case DENSITY_PREVIEW_NATIVE_ARC: 798 | previewRoundPixel(scaledPos, pixelSize*0.8); 799 | previewNativeArcPixel(scaledPos, pixelSize, brightness); 800 | break; 801 | default: 802 | previewRoundPixel(scaledPos, pixelSize); 803 | break; 804 | } 805 | } 806 | } 807 | } 808 | noFill(); 809 | } 810 | 811 | void previewDiamondPixel(PVector pos, float wide, float high, float brightness) 812 | { 813 | wide*=1.4; 814 | high*=1.4; 815 | // shall I try and draw a diamond here instead? OK! I'll do it! Ha! 816 | float halfWidth = wide / 2.0; 817 | float halfHeight = high / 2.0; 818 | fill(0,0,0, 255-brightness); 819 | quad(pos.x, pos.y-halfHeight, pos.x+halfWidth, pos.y, pos.x, pos.y+halfHeight, pos.x-halfWidth, pos.y); 820 | 821 | } 822 | 823 | void previewNativePixel(PVector pos, float size, float brightness) 824 | { 825 | float half = size / 2.0; 826 | 827 | // arcs from the left-hand corner 828 | float distFromPointA = getOutline().getTopLeft().dist(pos); 829 | float distFromPointB = getOutline().getTopRight().dist(pos); 830 | 831 | List int1 = findIntersections(getOutline().getLeft(), distFromPointA-half, getOutline().getRight(), distFromPointB-half, size); 832 | List int2 = findIntersections(getOutline().getLeft(), distFromPointA+half, getOutline().getRight(), distFromPointB-half, size); 833 | 834 | if (!int1.isEmpty() && !int2.isEmpty()) { 835 | fill(0,0,0, 255-brightness); 836 | beginShape(); 837 | 838 | // plot out the vertexes 839 | vertex(int1.get(0).x, int1.get(0).y); 840 | vertex(int2.get(0).x, int2.get(0).y); 841 | vertex(int2.get(1).x, int2.get(1).y); 842 | vertex(int1.get(1).x, int1.get(1).y); 843 | vertex(int1.get(0).x, int1.get(0).y); 844 | endShape(); 845 | } 846 | } 847 | 848 | void previewNativeArcPixel(PVector pos, float size, float brightness) 849 | { 850 | float half = size / 2.0; 851 | // fill(0,0,0, 255-brightness); 852 | beginShape(); 853 | 854 | // arcs from the left-hand corner 855 | float distFromPointA = getOutline().getTopLeft().dist(pos); 856 | float distFromPointB = getOutline().getTopRight().dist(pos); 857 | 858 | List int1 = findIntersections(getOutline().getLeft(), distFromPointA-half, getOutline().getRight(), distFromPointB-half, size); 859 | List int2 = findIntersections(getOutline().getLeft(), distFromPointA+half, getOutline().getRight(), distFromPointB-half, size); 860 | 861 | // plot out the vertexes 862 | noFill(); 863 | stroke(0,0,0, 255-brightness); 864 | try { 865 | 866 | float i1Angle1 = atan2(int1.get(0).y-getOutline().getTop(), int1.get(0).x-getOutline().getLeft()); 867 | float i1Angle2 = atan2(int1.get(1).y-getOutline().getTop(), int1.get(1).x-getOutline().getLeft()); 868 | arc(getOutline().getLeft(), getOutline().getTop(), (distFromPointA-half)*2, (distFromPointA-half)*2, i1Angle1, i1Angle2); 869 | 870 | i1Angle1 = atan2(int2.get(0).y-getOutline().getTop(), int2.get(0).x-getOutline().getLeft()); 871 | i1Angle2 = atan2(int2.get(1).y-getOutline().getTop(), int2.get(1).x-getOutline().getLeft()); 872 | arc(getOutline().getLeft(), getOutline().getTop(), (distFromPointA+half)*2, (distFromPointA+half)*2, i1Angle1, i1Angle2); 873 | 874 | i1Angle1 = atan2( int1.get(0).y-getOutline().getTop(), int1.get(0).x-getOutline().getRight()); 875 | i1Angle2 = atan2( int2.get(0).y-getOutline().getTop(), int2.get(0).x-getOutline().getRight()); 876 | arc(getOutline().getRight(), getOutline().getTop(), (distFromPointB-half)*2, (distFromPointB-half)*2, i1Angle2, i1Angle1); 877 | 878 | i1Angle1 = atan2( int1.get(1).y-getOutline().getTop(), int1.get(1).x-getOutline().getRight()); 879 | i1Angle2 = atan2( int2.get(1).y-getOutline().getTop(), int2.get(1).x-getOutline().getRight()); 880 | arc(getOutline().getRight(), getOutline().getTop(), (distFromPointB+half)*2, (distFromPointB+half)*2, i1Angle2, i1Angle1); 881 | } 882 | catch (IndexOutOfBoundsException ioobe) { 883 | println(ioobe); 884 | } 885 | finally { 886 | endShape(); 887 | } 888 | } 889 | 890 | 891 | 892 | 893 | void previewRoundPixel(PVector pos, float dia) 894 | { 895 | ellipse(pos.x, pos.y, dia*1.1, dia*1.1); 896 | } 897 | 898 | // compute and draw intersections 899 | /** 900 | circle1 = c1x is the centre, and r1 is the radius of the arc to be drawn. 901 | circle2 = c2x, r2 describe the arc that is used to calculate the start and 902 | end point of the drawn arc. 903 | 904 | circle3 = c2x, r3 is calculated by adding size to r2. 905 | 906 | The drawn arc should start at the intersection with the circle1, 907 | and end at the intersection with circle3. 908 | 909 | The clever bits of this are nicked off http://processing.org/discourse/beta/num_1223494826.html 910 | */ 911 | List findIntersections(float c1x, float r1, float c2x, float r2, float pixelSize) 912 | { 913 | float c1y = getOutline().getTop(); 914 | float c2y = getOutline().getTop(); 915 | float d=getOutline().getWidth(); // distance between centers 916 | float base1, h1, base2, h2; // auxiliary distances 917 | // p, middle point between q1 and q2 918 | // q1 dn q2 intersection points 919 | float p1x,p1y,p2x,p2y, q1x,q1y,q2x,q2y; 920 | 921 | if(dr1+r2) 922 | { 923 | println("C1 and C2 do not intersect"); 924 | return new ArrayList(); 925 | } 926 | else if(d==r1+r2) 927 | { // outside each other, intersect in one point 928 | return new ArrayList(); 929 | } 930 | else 931 | { 932 | // intersect in two points 933 | base1 = (r1*r1-r2*r2+d*d) / (2*d); 934 | h1 = sqrt(r1*r1-base1*base1); 935 | 936 | p1x = c1x+base1*(c2x-c1x)/d; 937 | p1y = c1y+base1*(c2y-c1y)/d; 938 | q1x=abs(p1x-h1*(c2y-c1y)/d); 939 | q1y=abs(p1y+h1*(c2x-c1x)/d); 940 | 941 | float r3 = r2+pixelSize; 942 | base2 = (r1*r1-r3*r3+d*d) / (2*d); 943 | h2 = sqrt(r1*r1-base2*base2); 944 | 945 | p2x = c1x+base2*(c2x-c1x)/d; 946 | p2y = c1y+base2*(c2y-c1y)/d; 947 | q2x=abs(p2x-h2*(c2y-c1y)/d); 948 | q2y=abs(p2y+h2*(c2x-c1x)/d); 949 | 950 | List l = new ArrayList(2); 951 | l.add(new PVector(q1x, q1y)); 952 | l.add(new PVector(q2x, q2y)); 953 | return l; 954 | } 955 | } 956 | 957 | color getPixelAtScreenCoords(PVector pos) 958 | { 959 | pos = scaleToDisplayMachine(pos); 960 | pos = inSteps(pos); 961 | float scalingFactor = getImage().width / getImageFrame().getWidth(); 962 | color col = super.getPixelAtMachineCoords(pos, scalingFactor); 963 | return col; 964 | } 965 | 966 | Set getExtractedPixels() 967 | { 968 | return this.extractedPixels; 969 | } 970 | void setExtractedPixels(Set p) 971 | { 972 | this.extractedPixels = p; 973 | } 974 | 975 | /* This will return a list of pixels that are included in the area in the 976 | parameter. All coordinates are for the screen. 977 | */ 978 | Set getPixelsPositionsFromArea(PVector p, PVector s, float rowSize) 979 | { 980 | extractPixelsFromArea(p, s, rowSize, 0.0); 981 | return getExtractedPixels(); 982 | } 983 | 984 | public void extractPixelsFromArea(PVector p, PVector s, float rowSize, float sampleSize) 985 | { 986 | // get the native positions from the superclass 987 | Set nativePositions = super.getPixelsPositionsFromArea(inSteps(p), inSteps(s), rowSize, sampleSize); 988 | 989 | // work out the cartesian positions 990 | Set cartesianPositions = new HashSet(nativePositions.size()); 991 | for (PVector nativePos : nativePositions) 992 | { 993 | // convert to cartesian 994 | PVector displayPos = super.asCartesianCoords(nativePos); 995 | displayPos = inMM(displayPos); 996 | displayPos.z = nativePos.z; 997 | cartesianPositions.add(displayPos); 998 | } 999 | setExtractedPixels(cartesianPositions); 1000 | } 1001 | 1002 | 1003 | public Set extractNativePixelsFromArea(PVector p, PVector s, float rowSize, float sampleSize) 1004 | { 1005 | // get the native positions from the superclass 1006 | Set nativePositions = super.getPixelsPositionsFromArea(inSteps(p), inSteps(s), rowSize, sampleSize); 1007 | return nativePositions; 1008 | } 1009 | 1010 | protected PVector snapToGrid(PVector loose, float rowSize) 1011 | { 1012 | PVector snapped = inSteps(loose); 1013 | snapped = super.snapToGrid(snapped, rowSize); 1014 | snapped = inMM(snapped); 1015 | return snapped; 1016 | } 1017 | 1018 | public boolean pixelsCanBeExtracted() 1019 | { 1020 | if (super.getImage() == null) 1021 | return false; 1022 | else 1023 | return true; 1024 | } 1025 | } 1026 | 1027 | -------------------------------------------------------------------------------- /DrawPixelsWindow.pde: -------------------------------------------------------------------------------- 1 | ///*------------------------------------------------------------------------ 2 | // Details about the "drawing" subwindow 3 | //------------------------------------------------------------------------*/ 4 | 5 | public Integer renderStartDirection = DRAW_DIR_SE; // default start drawing in SE direction (DOWN) 6 | public Integer renderStartPosition = DRAW_DIR_NE; // default top right hand corner for start 7 | public Integer renderStyle = PIXEL_STYLE_SQ_FREQ; // default pixel style square wave 8 | 9 | ControlFrameSimple addDrawPixelsControlFrame(String theName, int theWidth, int theHeight, int theX, int theY, int theColor ) { 10 | final Frame f = new Frame( theName ); 11 | final ControlFrameSimple p = new ControlFrameSimple( this, theWidth, theHeight, theColor ); 12 | 13 | f.add( p ); 14 | p.init(); 15 | f.setTitle(theName); 16 | f.setSize( p.w, p.h ); 17 | f.setLocation( theX, theY ); 18 | f.addWindowListener( new WindowAdapter() { 19 | @Override 20 | public void windowClosing(WindowEvent we) { 21 | p.dispose(); 22 | f.dispose(); 23 | } 24 | } 25 | ); 26 | f.setResizable( true ); 27 | f.setVisible( true ); 28 | // sleep a little bit to allow p to call setup. 29 | // otherwise a nullpointerexception might be caused. 30 | try { 31 | Thread.sleep( 100 ); 32 | } 33 | catch(Exception e) { 34 | } 35 | 36 | // set up controls 37 | RadioButton rPos = p.cp5().addRadioButton("radio_startPosition",10,10) 38 | .add("Top-right", DRAW_DIR_NE) 39 | .add("Bottom-right", DRAW_DIR_SE) 40 | .add("Bottom-left", DRAW_DIR_SW) 41 | .add("Top-left", DRAW_DIR_NW) 42 | .plugTo(this, "radio_startPosition"); 43 | 44 | RadioButton rSkip = p.cp5().addRadioButton("radio_pixelSkipStyle",10,100) 45 | .add("Lift pen over masked pixels", 1) 46 | .add("Draw masked pixels as blanks", 2) 47 | .plugTo(this, "radio_pixelSkipStyle"); 48 | 49 | RadioButton rStyle = p.cp5().addRadioButton("radio_pixelStyle",100,10); 50 | rStyle.add("Variable frequency square wave", PIXEL_STYLE_SQ_FREQ); 51 | rStyle.add("Variable size square wave", PIXEL_STYLE_SQ_SIZE); 52 | rStyle.add("Solid square wave", PIXEL_STYLE_SQ_SOLID); 53 | rStyle.add("Scribble", PIXEL_STYLE_SCRIBBLE); 54 | if (currentHardware >= HARDWARE_VER_MEGA) { 55 | rStyle.add("Spiral", PIXEL_STYLE_CIRCLE); 56 | rStyle.add("Sawtooth", PIXEL_STYLE_SAW); 57 | } 58 | rStyle.plugTo(this, "radio_pixelStyle"); 59 | 60 | 61 | Button submitButton = p.cp5().addButton("submitDrawWindow",0,280,10,120,20) 62 | .setLabel("Generate commands") 63 | .plugTo(this, "submitDrawWindow"); 64 | 65 | return p; 66 | } 67 | 68 | void radio_startPosition(int pos) { 69 | renderStartPosition = pos; 70 | radio_rowStartDirection(1); 71 | } 72 | 73 | void radio_rowStartDirection(int dir) { 74 | if (renderStartPosition == DRAW_DIR_NE || renderStartPosition == DRAW_DIR_SW) 75 | renderStartDirection = (dir == 0) ? DRAW_DIR_NW : DRAW_DIR_SE; 76 | else if (renderStartPosition == DRAW_DIR_SE || renderStartPosition == DRAW_DIR_NW) 77 | renderStartDirection = (dir == 0) ? DRAW_DIR_NE : DRAW_DIR_SW; 78 | } 79 | 80 | void radio_pixelStyle(int style) { 81 | renderStyle = style; 82 | } 83 | 84 | void radio_pixelSkipStyle(int style) { 85 | if (style == 1) 86 | liftPenOnMaskedPixels = true; 87 | else if (style == 2) 88 | liftPenOnMaskedPixels = false; 89 | } 90 | 91 | void submitDrawWindow(int theValue) { 92 | println("draw."); 93 | println("Style: " + renderStyle); 94 | println("Start pos: " + renderStartPosition); 95 | println("Start dir: " + renderStartDirection); 96 | 97 | switch (renderStyle) { 98 | case PIXEL_STYLE_SQ_FREQ: button_mode_renderSquarePixel(); break; 99 | case PIXEL_STYLE_SQ_SIZE: button_mode_renderScaledSquarePixels(); break; 100 | case PIXEL_STYLE_SQ_SOLID: button_mode_renderSolidSquarePixels(); break; 101 | case PIXEL_STYLE_SCRIBBLE: button_mode_renderScribblePixels(); break; 102 | case PIXEL_STYLE_CIRCLE: button_mode_renderCirclePixel(); break; 103 | case PIXEL_STYLE_SAW: button_mode_renderSawPixel(); break; 104 | } 105 | } 106 | 107 | 108 | class DrawPixelsWindow extends ControlFrame { 109 | 110 | 111 | public DrawPixelsWindow () { 112 | super(parentPapplet, 450, 150); 113 | 114 | int xPos = 100; 115 | int yPos = 100; 116 | String name = DRAW_PIXELS_WINDOW_NAME; 117 | 118 | final Frame f = new Frame(DRAW_PIXELS_WINDOW_NAME); 119 | f.add(this); 120 | this.init(); 121 | f.setTitle(CHANGE_SERIAL_PORT_WINDOW_NAME); 122 | f.setSize(super.w, super.h); 123 | f.setLocation(xPos, yPos); 124 | f.setResizable(false); 125 | f.setVisible(true); 126 | 127 | f.addWindowListener( new WindowAdapter() { 128 | @Override 129 | public void windowClosing(WindowEvent we) { 130 | f.dispose(); 131 | } 132 | }); 133 | 134 | 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /FileLoading.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2018. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | 30 | 31 | 32 | void loadImageWithFileChooser() 33 | { 34 | SwingUtilities.invokeLater(new Runnable() 35 | { 36 | public void run() { 37 | JFileChooser fc = new JFileChooser(); 38 | if (lastImageDirectory != null) fc.setCurrentDirectory(lastImageDirectory); 39 | fc.setFileFilter(new ImageFileFilter()); 40 | fc.setDialogTitle("Choose an image file..."); 41 | 42 | int returned = fc.showOpenDialog(frame); 43 | 44 | lastImageDirectory = fc.getCurrentDirectory(); 45 | 46 | if (returned == JFileChooser.APPROVE_OPTION) 47 | { 48 | File file = fc.getSelectedFile(); 49 | // see if it's an image 50 | PImage img = loadImage(file.getPath()); 51 | if (img != null) 52 | { 53 | img = null; 54 | getDisplayMachine().loadNewImageFromFilename(file.getPath()); 55 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 56 | { 57 | getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 58 | } 59 | } 60 | } 61 | } 62 | }); 63 | } 64 | 65 | class ImageFileFilter extends javax.swing.filechooser.FileFilter 66 | { 67 | public boolean accept(File file) { 68 | String filename = file.getName(); 69 | filename.toLowerCase(); 70 | if (file.isDirectory() || filename.endsWith(".png") || filename.endsWith(".jpg") || filename.endsWith(".jpeg")) 71 | return true; 72 | else 73 | return false; 74 | } 75 | public String getDescription() { 76 | return "Image files (PNG or JPG)"; 77 | } 78 | } 79 | 80 | void loadVectorWithFileChooser() 81 | { 82 | SwingUtilities.invokeLater(new Runnable() 83 | { 84 | public void run() { 85 | JFileChooser fc = new JFileChooser(); 86 | if (lastImageDirectory != null) 87 | { 88 | fc.setCurrentDirectory(lastImageDirectory); 89 | } 90 | 91 | fc.setFileFilter(new VectorFileFilter()); 92 | fc.setDialogTitle("Choose a vector file..."); 93 | int returned = fc.showOpenDialog(frame); 94 | lastImageDirectory = fc.getCurrentDirectory(); 95 | 96 | if (returned == JFileChooser.APPROVE_OPTION) 97 | { 98 | File file = fc.getSelectedFile(); 99 | if (file.exists()) 100 | { 101 | RShape shape = loadShapeFromFile(file.getPath()); 102 | if (shape != null) 103 | { 104 | setVectorFilename(file.getPath()); 105 | setVectorShape(shape); 106 | } 107 | else 108 | { 109 | println("File not found (" + file.getPath() + ")"); 110 | } 111 | } 112 | } 113 | } 114 | } 115 | ); 116 | } 117 | 118 | class VectorFileFilter extends javax.swing.filechooser.FileFilter 119 | { 120 | public boolean accept(File file) { 121 | String filename = file.getName(); 122 | filename.toLowerCase(); 123 | if (file.isDirectory() || filename.endsWith(".svg") || isGCodeExtension(filename)) 124 | return true; 125 | else 126 | return false; 127 | } 128 | public String getDescription() { 129 | return "Vector graphic files (SVG, GCode)"; 130 | } 131 | } 132 | 133 | void loadNewPropertiesFilenameWithFileChooser() 134 | { 135 | SwingUtilities.invokeLater(new Runnable() 136 | { 137 | public void run() 138 | { 139 | JFileChooser fc = new JFileChooser(); 140 | if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); 141 | fc.setFileFilter(new PropertiesFileFilter()); 142 | 143 | fc.setDialogTitle("Choose a config file..."); 144 | 145 | int returned = fc.showOpenDialog(frame); 146 | 147 | lastPropertiesDirectory = fc.getCurrentDirectory(); 148 | 149 | if (returned == JFileChooser.APPROVE_OPTION) 150 | { 151 | File file = fc.getSelectedFile(); 152 | if (file.exists()) 153 | { 154 | println("New properties file exists."); 155 | newPropertiesFilename = file.toString(); 156 | println("new propertiesFilename: "+ newPropertiesFilename); 157 | propertiesFilename = newPropertiesFilename; 158 | // clear old properties. 159 | props = null; 160 | loadFromPropertiesFile(); 161 | 162 | // set values of number spinners etc 163 | updateNumberboxValues(); 164 | } 165 | } 166 | } 167 | }); 168 | } 169 | 170 | class PropertiesFileFilter extends javax.swing.filechooser.FileFilter 171 | { 172 | public boolean accept(File file) { 173 | String filename = file.getName(); 174 | filename.toLowerCase(); 175 | if (file.isDirectory() || filename.endsWith(".properties.txt")) 176 | return true; 177 | else 178 | return false; 179 | } 180 | public String getDescription() { 181 | return "Properties files (*.properties.txt)"; 182 | } 183 | } 184 | 185 | void saveNewPropertiesFileWithFileChooser() 186 | { 187 | SwingUtilities.invokeLater(new Runnable() 188 | { 189 | public void run() 190 | { 191 | JFileChooser fc = new JFileChooser(); 192 | if (lastPropertiesDirectory != null) fc.setCurrentDirectory(lastPropertiesDirectory); 193 | fc.setFileFilter(new PropertiesFileFilter()); 194 | 195 | fc.setDialogTitle("Enter a config file name..."); 196 | 197 | int returned = fc.showSaveDialog(frame); 198 | if (returned == JFileChooser.APPROVE_OPTION) 199 | { 200 | File file = fc.getSelectedFile(); 201 | newPropertiesFilename = file.toString(); 202 | newPropertiesFilename.toLowerCase(); 203 | if (!newPropertiesFilename.endsWith(".properties.txt")) 204 | newPropertiesFilename+=".properties.txt"; 205 | 206 | println("new propertiesFilename: "+ newPropertiesFilename); 207 | propertiesFilename = newPropertiesFilename; 208 | savePropertiesFile(); 209 | // clear old properties. 210 | props = null; 211 | loadFromPropertiesFile(); 212 | } 213 | } 214 | }); 215 | } 216 | 217 | 218 | 219 | RShape loadShapeFromFile(String filename) { 220 | RShape sh = null; 221 | if (filename.toLowerCase().endsWith(".svg")) { 222 | sh = RG.loadShape(filename); 223 | } 224 | else if (isGCodeExtension(filename)) { 225 | sh = loadShapeFromGCodeFile(filename); 226 | } 227 | return sh; 228 | } 229 | 230 | 231 | boolean isGCodeExtension(String filename) { 232 | return (filename.toLowerCase().endsWith(".gcode") || filename.toLowerCase().endsWith(".g") || filename.toLowerCase().endsWith(".ngc") || filename.toLowerCase().endsWith(".txt")); 233 | } 234 | 235 | 236 | int countLines(String filename) throws IOException { 237 | InputStream is = new BufferedInputStream(new FileInputStream(filename)); 238 | try { 239 | byte[] c = new byte[1024]; 240 | int count = 0; 241 | int readChars = 0; 242 | boolean empty = true; 243 | while ((readChars = is.read(c)) != -1) { 244 | empty = false; 245 | for (int i = 0; i < readChars; ++i) { 246 | if (c[i] == '\n') { 247 | ++count; 248 | } 249 | } 250 | } 251 | return (count == 0 && !empty) ? 1 : count+1; 252 | } finally { 253 | is.close(); 254 | } 255 | } 256 | 257 | RShape loadShapeFromGCodeFile(String filename) { 258 | noLoop(); 259 | RShape parent = null; 260 | BufferedReader reader = null; 261 | long totalPoints = 0; 262 | long time = millis(); 263 | long countLines = 0; 264 | 265 | try { 266 | countLines = countLines(filename); 267 | println("" + countLines + " lines found."); 268 | if (countLines < 1) { 269 | throw new IOException("No lines found in GCode file."); 270 | } 271 | reader = createReader(filename); 272 | parent = new RShape(); 273 | String line; 274 | boolean drawLine = false; 275 | int gCodeZAxisChanges = 0; 276 | 277 | long lineNo = 0; 278 | float lastPercent = 0.0f; 279 | boolean reportStatus = true; 280 | while ((line = reader.readLine ()) != null) { 281 | lineNo++; 282 | // println("Line: " + line); 283 | 284 | if (reportStatus) { 285 | float percent = ((float)lineNo / (float)countLines) * 100.0; 286 | println("----" + percent + "% of the way through."); 287 | lastPercent = percent; 288 | } 289 | 290 | if (line.toUpperCase().startsWith("G")) { 291 | if (reportStatus) { 292 | println(new StringBuilder().append(lineNo).append(" of ").append(countLines).append(": ").append(line).append(". Points: ").append(totalPoints).toString()); 293 | long free = Runtime.getRuntime().freeMemory(); 294 | long maximum = Runtime.getRuntime().maxMemory(); 295 | println(new StringBuilder().append("Free: ").append(free).append(", max: ").append(maximum).toString()); 296 | } 297 | 298 | Map ins = null; 299 | try { 300 | ins = unpackGCodeInstruction(line); 301 | } 302 | catch (Exception e) { 303 | println(e.toString()); 304 | continue; 305 | } 306 | // println("Ins: " + ins); 307 | Integer code = Math.round(ins.get("G")); 308 | 309 | Float z = ins.get("Z"); 310 | if (z != null) { 311 | gCodeZAxisChanges++; 312 | if (gCodeZAxisChanges == 2) { 313 | println("Assume second z axis change is to drop the pen to start drawing " + z); 314 | gcodeZAxisDrawingHeight = z; 315 | drawLine = true; 316 | } 317 | else if (gCodeZAxisChanges > 2) { 318 | drawLine = isGCodeZAxisForDrawing(z); 319 | } 320 | else { 321 | println("Assume first z axis change is to RAISE the pen " + z); 322 | drawLine = false; 323 | } 324 | } 325 | else { // if there is no Z axis, assume it's always on 326 | // drawLine = true; // this isn't always safe! 327 | } 328 | 329 | Float x = ins.get("X"); 330 | Float y = ins.get("Y"); 331 | if (x != null && y == null) { 332 | // move x axis only, use y of last 333 | RPoint[][] points = parent.getPointsInPaths(); 334 | RPoint rp = points[points.length-1][points[points.length-1].length-1]; 335 | y = rp.y; 336 | } 337 | else if (x == null && y != null) { 338 | // move y axis only, use x of last 339 | RPoint[][] points = parent.getPointsInPaths(); 340 | RPoint rp = points[points.length-1][points[points.length-1].length-1]; 341 | x = rp.x; 342 | } 343 | 344 | if (x != null && y != null) { 345 | // move both x and y axis 346 | if (drawLine) { 347 | parent.addLineTo(x, y); 348 | } 349 | else { 350 | parent.addMoveTo(x, y); 351 | } 352 | } 353 | } 354 | else { 355 | 356 | } 357 | 358 | if ((millis() - time) > 500) { 359 | time = millis(); 360 | reportStatus = true; 361 | } 362 | else { 363 | reportStatus = false; 364 | } 365 | 366 | if (lineNo == (countLines-1)) { 367 | reportStatus = true; 368 | } 369 | 370 | } 371 | } 372 | catch (IOException e) { 373 | println("IOExecption reading lines from the gcode file " + filename); 374 | e.printStackTrace(); 375 | } 376 | finally { 377 | try { 378 | reader.close(); 379 | } 380 | catch (IOException e) { 381 | println("IOException closing the gcode file " + filename); 382 | e.printStackTrace(); 383 | } 384 | } 385 | 386 | RPoint[][] points = parent.getPointsInPaths(); 387 | totalPoints = 0; 388 | if (points != null) { 389 | for (int i = 0; i unpackGCodeInstruction(String line) throws Exception { 411 | Map instruction = new HashMap(4); 412 | try { 413 | String[] splitted = line.trim().split(" "); 414 | for (int i = 0; i < splitted.length; i++) { 415 | // remove ; character 416 | splitted[i] = splitted[i].replace(";", ""); 417 | String axis = splitted[i].substring(0, 1); 418 | String sanitisedValue = splitted[i].substring(1); 419 | sanitisedValue = sanitisedValue.replace(",", "."); 420 | Float value = Float.parseFloat(sanitisedValue); 421 | if ("X".equalsIgnoreCase(axis) || "Y".equalsIgnoreCase(axis) || "Z".equalsIgnoreCase(axis) || "G".equalsIgnoreCase(axis)) { 422 | instruction.put(axis.toUpperCase(), value); 423 | } 424 | } 425 | // println("instruction: " + instruction); 426 | if (instruction.isEmpty()) { 427 | throw new Exception("Empty instruction"); 428 | } 429 | } 430 | catch (NumberFormatException nfe) { 431 | println("Number format exception: " + nfe.getMessage()); 432 | } 433 | catch (Exception e) { 434 | println("e: " + e); 435 | throw new Exception("Exception while reading the lines from a gcode file: " + line + ", " + e.getMessage()); 436 | } 437 | 438 | return instruction; 439 | } 440 | -------------------------------------------------------------------------------- /Machine.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | /** 30 | * 31 | * 32 | * 33 | */ 34 | class Machine 35 | { 36 | protected PVector machineSize = new PVector(4000,6000); 37 | 38 | protected Rectangle page = new Rectangle(1000,1000,2000,3000); 39 | protected Rectangle imageFrame = new Rectangle(1500,1500,1000,1000); 40 | protected Rectangle pictureFrame = new Rectangle(1600,1600,800,800); 41 | 42 | protected Float stepsPerRev = 200.0; 43 | protected Float mmPerRev = 95.0; 44 | 45 | protected Float mmPerStep = null; 46 | protected Float stepsPerMM = null; 47 | protected Float maxLength = null; 48 | protected Float gridSize = 100.0; 49 | protected List gridLinePositions = null; 50 | 51 | protected PImage imageBitmap = null; 52 | protected String imageFilename = null; 53 | 54 | 55 | public Machine(Integer width, Integer height, Float stepsPerRev, Float mmPerRev) 56 | { 57 | this.setSize(width, height); 58 | this.setStepsPerRev(stepsPerRev); 59 | this.setMMPerRev(mmPerRev); 60 | } 61 | 62 | public void setSize(Integer width, Integer height) 63 | { 64 | PVector s = new PVector(width, height); 65 | this.machineSize = s; 66 | maxLength = null; 67 | } 68 | public PVector getSize() 69 | { 70 | return this.machineSize; 71 | } 72 | public Float getMaxLength() 73 | { 74 | if (maxLength == null) 75 | { 76 | maxLength = dist(0,0, getWidth(), getHeight()); 77 | } 78 | return maxLength; 79 | } 80 | 81 | public void setPage(Rectangle r) 82 | { 83 | this.page = r; 84 | } 85 | public Rectangle getPage() 86 | { 87 | return this.page; 88 | } 89 | public float getPageCentrePosition(float pageWidth) 90 | { 91 | return (getWidth()- pageWidth/2)/2; 92 | } 93 | 94 | public void setImageFrame(Rectangle r) 95 | { 96 | this.imageFrame = r; 97 | } 98 | 99 | public Rectangle getImageFrame() 100 | { 101 | return this.imageFrame; 102 | } 103 | 104 | public void setPictureFrame(Rectangle r) 105 | { 106 | this.pictureFrame = r; 107 | } 108 | public Rectangle getPictureFrame() 109 | { 110 | return this.pictureFrame; 111 | } 112 | 113 | public Integer getWidth() 114 | { 115 | return int(this.machineSize.x); 116 | } 117 | public Integer getHeight() 118 | { 119 | return int(this.machineSize.y); 120 | } 121 | 122 | public void setStepsPerRev(Float s) 123 | { 124 | this.stepsPerRev = s; 125 | } 126 | public Float getStepsPerRev() 127 | { 128 | mmPerStep = null; 129 | stepsPerMM = null; 130 | return this.stepsPerRev; 131 | } 132 | public void setMMPerRev(Float d) 133 | { 134 | mmPerStep = null; 135 | stepsPerMM = null; 136 | this.mmPerRev = d; 137 | } 138 | public Float getMMPerRev() 139 | { 140 | return this.mmPerRev; 141 | } 142 | public Float getMMPerStep() 143 | { 144 | if (mmPerStep == null) 145 | { 146 | mmPerStep = mmPerRev / stepsPerRev; 147 | } 148 | return mmPerStep; 149 | } 150 | public Float getStepsPerMM() 151 | { 152 | if (stepsPerMM == null) 153 | { 154 | stepsPerMM = stepsPerRev / mmPerRev; 155 | } 156 | return stepsPerMM; 157 | } 158 | 159 | public int inSteps(int inMM) 160 | { 161 | double steps = inMM * getStepsPerMM(); 162 | steps += 0.5; 163 | int stepsInt = (int) steps; 164 | return stepsInt; 165 | } 166 | 167 | public int inSteps(float inMM) 168 | { 169 | double steps = inMM * getStepsPerMM(); 170 | steps += 0.5; 171 | int stepsInt = (int) steps; 172 | return stepsInt; 173 | } 174 | 175 | public PVector inSteps(PVector mm) 176 | { 177 | PVector steps = new PVector(inSteps(mm.x), inSteps(mm.y)); 178 | return steps; 179 | } 180 | 181 | public int inMM(float steps) 182 | { 183 | double mm = steps / getStepsPerMM(); 184 | mm += 0.5; 185 | int mmInt = (int) mm; 186 | return mmInt; 187 | } 188 | 189 | public float inMMFloat(float steps) 190 | { 191 | double mm = steps / getStepsPerMM(); 192 | return (float) mm; 193 | } 194 | 195 | public PVector inMM (PVector steps) 196 | { 197 | PVector mm = new PVector(inMMFloat(steps.x), inMMFloat(steps.y)); 198 | return mm; 199 | } 200 | 201 | float getPixelBrightness(PVector pos, float dim, float scalingFactor) 202 | { 203 | float averageBrightness = 255.0; 204 | 205 | if (getImageFrame().surrounds(pos)) 206 | { 207 | // offset it by image position to get position over image 208 | PVector offsetPos = PVector.sub(pos, getImageFrame().getPosition()); 209 | int originX = (int) offsetPos.x; 210 | int originY = (int) offsetPos.y; 211 | 212 | PImage extractedPixels = null; 213 | 214 | extractedPixels = getImage().get(int(originX*scalingFactor), int(originY*scalingFactor), 1, 1); 215 | extractedPixels.loadPixels(); 216 | 217 | if (dim >= 2) 218 | { 219 | int halfDim = (int)dim / (int)2.0; 220 | 221 | // restrict the sample area from going off the top/left edge of the image 222 | float startX = originX - halfDim; 223 | float startY = originY - halfDim; 224 | 225 | if (startX < 0) 226 | startX = 0; 227 | 228 | if (startY < 0) 229 | startY = 0; 230 | 231 | // and do the same for the bottom / right edges 232 | float endX = originX+halfDim; 233 | float endY = originY+halfDim; 234 | 235 | if (endX > getImageFrame().getWidth()) 236 | endX = getImageFrame().getWidth(); 237 | 238 | if (endY > getImageFrame().getHeight()) 239 | endY = getImageFrame().getHeight(); 240 | 241 | // now convert end coordinates to width/height 242 | float dimWidth = (endX - startX)*scalingFactor; 243 | float dimHeight = (endY - startY)*scalingFactor; 244 | 245 | dimWidth = (dimWidth < 1.0) ? 1.0 : dimWidth; 246 | dimHeight = (dimHeight < 1.0) ? 1.0 : dimHeight; 247 | startX = int(startX*scalingFactor); 248 | startY = int(startY*scalingFactor); 249 | 250 | // get the block of pixels 251 | extractedPixels = getImage().get(int(startX), int(startY), int(dimWidth+0.5), int(dimHeight+0.5)); 252 | extractedPixels.loadPixels(); 253 | } 254 | 255 | // going to go through them and total the brightnesses 256 | int numberOfPixels = extractedPixels.pixels.length; 257 | float totalPixelBrightness = 0; 258 | for (int i = 0; i < numberOfPixels; i++) 259 | { 260 | color p = extractedPixels.pixels[i]; 261 | float r = brightness(p); 262 | totalPixelBrightness += r; 263 | } 264 | 265 | // and get an average brightness for all of these pixels. 266 | averageBrightness = totalPixelBrightness / numberOfPixels; 267 | } 268 | 269 | return averageBrightness; 270 | } 271 | 272 | color getPixelAtMachineCoords(PVector pos, float scalingFactor) 273 | { 274 | if (getImageFrame().surrounds(pos)) 275 | { 276 | // offset it by image position to get position over image 277 | PVector offsetPos = PVector.sub(pos, getImageFrame().getPosition()); 278 | int originX = (int) offsetPos.x; 279 | int originY = (int) offsetPos.y; 280 | 281 | PImage centrePixel = null; 282 | 283 | centrePixel = getImage().get(int(originX*scalingFactor), int(originY*scalingFactor), 1, 1); 284 | centrePixel.loadPixels(); 285 | 286 | color col = centrePixel.pixels[0]; 287 | return col; 288 | } 289 | else 290 | { 291 | return 0; 292 | } 293 | } 294 | 295 | boolean isMasked(PVector pos, float scalingFactor) 296 | { 297 | switch (invertMaskMode) { 298 | case MASK_IS_UNUSED: return false; 299 | case MASKED_COLOURS_ARE_HIDDEN: return isChromaKey(pos, scalingFactor); 300 | case MASKED_COLOURS_ARE_SHOWN: return !isChromaKey(pos, scalingFactor); 301 | default: return false; 302 | } 303 | } 304 | 305 | boolean isChromaKey(PVector pos, float scalingFactor) 306 | { 307 | if (getImageFrame().surrounds(pos)) 308 | { 309 | color col = getPixelAtMachineCoords(pos, scalingFactor); 310 | 311 | // get pixels from the vector coords 312 | if (col == chromaKeyColour) 313 | { 314 | // println("is chroma key " + red(col) + ", "+green(col)+","+blue(col)); 315 | return true; 316 | } 317 | else 318 | { 319 | // println("isn't chroma key " + red(col) + ", "+green(col)+","+blue(col)); 320 | return false; 321 | } 322 | } 323 | else return false; 324 | } 325 | 326 | public PVector asNativeCoords(PVector cartCoords) 327 | { 328 | return asNativeCoords(cartCoords.x, cartCoords.y); 329 | } 330 | public PVector asNativeCoords(float cartX, float cartY) 331 | { 332 | float distA = dist(0,0,cartX, cartY); 333 | float distB = dist(getWidth(),0,cartX, cartY); 334 | PVector pgCoords = new PVector(distA, distB); 335 | return pgCoords; 336 | } 337 | 338 | 339 | public PVector asCartesianCoords(PVector pgCoords) 340 | { 341 | float calcX = (pow(getWidth(), 2.0) - pow(pgCoords.y, 2.0) + pow(pgCoords.x, 2.0)) / (getWidth()*2.0); 342 | float calcY = sqrt(pow(pgCoords.x,2.0)-pow(calcX,2.0)); 343 | PVector vect = new PVector(calcX, calcY); 344 | return vect; 345 | } 346 | 347 | public Integer convertSizePreset(String preset) 348 | { 349 | Integer result = A3_SHORT; 350 | if (preset.equalsIgnoreCase(PRESET_A3_SHORT)) 351 | result = A3_SHORT; 352 | else if (preset.equalsIgnoreCase(PRESET_A3_LONG)) 353 | result = A3_LONG; 354 | else if (preset.equalsIgnoreCase(PRESET_A2_SHORT)) 355 | result = A2_SHORT; 356 | else if (preset.equalsIgnoreCase(PRESET_A2_LONG)) 357 | result = A2_LONG; 358 | else if (preset.equalsIgnoreCase(PRESET_A2_IMP_SHORT)) 359 | result = A2_IMP_SHORT; 360 | else if (preset.equalsIgnoreCase(PRESET_A2_IMP_LONG)) 361 | result = A2_IMP_LONG; 362 | else if (preset.equalsIgnoreCase(PRESET_A1_SHORT)) 363 | result = A1_SHORT; 364 | else if (preset.equalsIgnoreCase(PRESET_A1_LONG)) 365 | result = A1_LONG; 366 | else 367 | { 368 | try 369 | { 370 | result = Integer.parseInt(preset); 371 | } 372 | catch (NumberFormatException nfe) 373 | { 374 | result = A3_SHORT; 375 | } 376 | } 377 | return result; 378 | } 379 | 380 | public void loadDefinitionFromProperties(Properties props) 381 | { 382 | // get these first because they are important to convert the rest of them 383 | setStepsPerRev(getFloatProperty("machine.motors.stepsPerRev", 200.0)); 384 | setMMPerRev(getFloatProperty("machine.motors.mmPerRev", 95.0)); 385 | 386 | // now stepsPerMM and mmPerStep should have been calculated. It's safe to get the rest. 387 | 388 | // machine size 389 | setSize(inSteps(getIntProperty("machine.width", 600)), inSteps(getIntProperty("machine.height", 800))); 390 | 391 | // page size 392 | String pageWidth = getStringProperty("controller.page.width", PRESET_A3_SHORT); 393 | float pw = convertSizePreset(pageWidth); 394 | String pageHeight = getStringProperty("controller.page.height", PRESET_A3_LONG); 395 | float ph = convertSizePreset(pageHeight); 396 | PVector pageSize = new PVector(pw, ph); 397 | 398 | // page position 399 | String pos = getStringProperty("controller.page.position.x", "CENTRE"); 400 | float px = 0.0; 401 | println("machine size: " + getSize().x + ", " + inSteps(pageSize.x)); 402 | if (pos.equalsIgnoreCase("CENTRE")) { 403 | px = inMM((getSize().x - pageSize.x) / 2.0); 404 | } 405 | else { 406 | px = getFloatProperty("controller.page.position.x", (int) getDisplayMachine().getPageCentrePosition(pageSize.x)); 407 | } 408 | 409 | float py = getFloatProperty("controller.page.position.y", 120); 410 | 411 | PVector pagePos = new PVector(px, py); 412 | Rectangle page = new Rectangle(inSteps(pagePos), inSteps(pageSize)); 413 | setPage(page); 414 | 415 | // bitmap 416 | setImageFilename(getStringProperty("controller.image.filename", "")); 417 | loadImageFromFilename(imageFilename); 418 | 419 | // image position 420 | Float offsetX = getFloatProperty("controller.image.position.x", 0.0); 421 | Float offsetY = getFloatProperty("controller.image.position.y", 0.0); 422 | PVector imagePos = new PVector(offsetX, offsetY); 423 | // println("image pos: " + imagePos); 424 | 425 | // image size 426 | Float imageWidth = getFloatProperty("controller.image.width", 500); 427 | Float imageHeight = getFloatProperty("controller.image.height", 0); 428 | if (imageHeight == 0) // default was set 429 | { 430 | println("Image height not supplied - creating default."); 431 | if (getImage() != null) 432 | { 433 | float scaling = imageWidth / getImage().width; 434 | imageHeight = getImage().height * scaling; 435 | } 436 | else 437 | imageHeight = 500.0; 438 | } 439 | PVector imageSize = new PVector(imageWidth, imageHeight); 440 | 441 | Rectangle imageFrame = new Rectangle(inSteps(imagePos), inSteps(imageSize)); 442 | setImageFrame(imageFrame); 443 | 444 | // picture frame size 445 | PVector frameSize = new PVector(getIntProperty("controller.pictureframe.width", 200), getIntProperty("controller.pictureframe.height", 200)); 446 | PVector framePos = new PVector(getIntProperty("controller.pictureframe.position.x", 200), getIntProperty("controller.pictureframe.position.y", 200)); 447 | Rectangle frame = new Rectangle(inSteps(framePos), inSteps(frameSize)); 448 | setPictureFrame(frame); 449 | 450 | // penlift positions 451 | penLiftDownPosition = getIntProperty("machine.penlift.down", 90); 452 | penLiftUpPosition = getIntProperty("machine.penlift.up", 180); 453 | } 454 | 455 | 456 | public Properties loadDefinitionIntoProperties(Properties props) 457 | { 458 | // Put keys into properties file: 459 | props.setProperty("machine.motors.stepsPerRev", getStepsPerRev().toString()); 460 | props.setProperty("machine.motors.mmPerRev", getMMPerRev().toString()); 461 | 462 | // machine width 463 | props.setProperty("machine.width", Integer.toString((int) inMM(getWidth()))); 464 | // machine.height 465 | props.setProperty("machine.height", Integer.toString((int) inMM(getHeight()))); 466 | 467 | // image filename 468 | props.setProperty("controller.image.filename", (getImageFilename() == null) ? "" : getImageFilename()); 469 | 470 | // image position 471 | float imagePosX = 0.0; 472 | float imagePosY = 0.0; 473 | float imageWidth = 0.0; 474 | float imageHeight = 0.0; 475 | if (getImageFrame() != null) 476 | { 477 | imagePosX = getImageFrame().getLeft(); 478 | imagePosY = getImageFrame().getTop(); 479 | imageWidth = getImageFrame().getWidth(); 480 | imageHeight = getImageFrame().getHeight(); 481 | } 482 | props.setProperty("controller.image.position.x", Integer.toString((int) inMM(imagePosX))); 483 | props.setProperty("controller.image.position.y", Integer.toString((int) inMM(imagePosY))); 484 | 485 | // image size 486 | props.setProperty("controller.image.width", Integer.toString((int) inMM(imageWidth))); 487 | props.setProperty("controller.image.height", Integer.toString((int) inMM(imageHeight))); 488 | 489 | // page size 490 | // page position 491 | float pageSizeX = 0.0; 492 | float pageSizeY = 0.0; 493 | float pagePosX = 0.0; 494 | float pagePosY = 0.0; 495 | if (getPage() != null) 496 | { 497 | pageSizeX = getPage().getWidth(); 498 | pageSizeY = getPage().getHeight(); 499 | pagePosX = getPage().getLeft(); 500 | pagePosY = getPage().getTop(); 501 | } 502 | props.setProperty("controller.page.width", Integer.toString((int) inMM(pageSizeX))); 503 | props.setProperty("controller.page.height", Integer.toString((int) inMM(pageSizeY))); 504 | props.setProperty("controller.page.position.x", Integer.toString((int) inMM(pagePosX))); 505 | props.setProperty("controller.page.position.y", Integer.toString((int) inMM(pagePosY))); 506 | 507 | // picture frame size 508 | float frameSizeX = 0.0; 509 | float frameSizeY = 0.0; 510 | float framePosX = 0.0; 511 | float framePosY = 0.0; 512 | if (getPictureFrame() != null) 513 | { 514 | frameSizeX = getPictureFrame().getWidth(); 515 | frameSizeY = getPictureFrame().getHeight(); 516 | framePosX = getPictureFrame().getLeft(); 517 | framePosY = getPictureFrame().getTop(); 518 | } 519 | props.setProperty("controller.pictureframe.width", Integer.toString((int) inMM(frameSizeX))); 520 | props.setProperty("controller.pictureframe.height", Integer.toString((int) inMM(frameSizeY))); 521 | 522 | // picture frame position 523 | props.setProperty("controller.pictureframe.position.x", Integer.toString((int) inMM(framePosX))); 524 | props.setProperty("controller.pictureframe.position.y", Integer.toString((int) inMM(framePosY))); 525 | 526 | props.setProperty("machine.penlift.down", Integer.toString((int) penLiftDownPosition)); 527 | props.setProperty("machine.penlift.up", Integer.toString((int) penLiftUpPosition)); 528 | 529 | // println("framesize: " + inMM(frameSizeX)); 530 | 531 | return props; 532 | } 533 | 534 | protected void loadImageFromFilename(String filename) 535 | { 536 | if (filename != null && !"".equals(filename)) 537 | { 538 | // check for format etc here 539 | println("loading from filename: " + filename); 540 | try 541 | { 542 | this.imageBitmap = loadImage(filename); 543 | this.imageFilename = filename; 544 | trace_initTrace(this.imageBitmap); 545 | } 546 | catch (Exception e) 547 | { 548 | println("Image failed to load: " + e.getMessage()); 549 | this.imageBitmap = null; 550 | } 551 | 552 | } 553 | else 554 | { 555 | this.imageBitmap = null; 556 | this.imageFilename = null; 557 | } 558 | } 559 | 560 | public void sizeImageFrameToImageAspectRatio() 561 | { 562 | float scaling = getImageFrame().getWidth() / getImage().width; 563 | float frameHeight = getImage().height * scaling; 564 | getImageFrame().getSize().y = frameHeight; 565 | } 566 | 567 | public void setImage(PImage b) 568 | { 569 | this.imageBitmap = b; 570 | } 571 | public void setImageFilename(String filename) 572 | { 573 | this.loadImageFromFilename(filename); 574 | } 575 | public String getImageFilename() 576 | { 577 | return this.imageFilename; 578 | } 579 | public PImage getImage() 580 | { 581 | return this.imageBitmap; 582 | } 583 | 584 | public boolean imageIsReady() 585 | { 586 | if (imageBitmapIsLoaded()) 587 | return true; 588 | else 589 | return false; 590 | } 591 | 592 | public boolean imageBitmapIsLoaded() 593 | { 594 | if (getImage() != null) 595 | return true; 596 | else 597 | return false; 598 | } 599 | 600 | 601 | protected void setGridSize(float gridSize) 602 | { 603 | this.gridSize = gridSize; 604 | this.gridLinePositions = generateGridLinePositions(gridSize); 605 | } 606 | 607 | /** 608 | This takes in an area defined in cartesian steps, 609 | and returns a set of pixels that are included 610 | in that area. Coordinates are specified 611 | in cartesian steps. The pixels are worked out 612 | based on the gridsize parameter. d*/ 613 | Set getPixelsPositionsFromArea(PVector p, PVector s, float gridSize, float sampleSize) 614 | { 615 | 616 | // work out the grid 617 | setGridSize(gridSize); 618 | float maxLength = getMaxLength(); 619 | float numberOfGridlines = maxLength / gridSize; 620 | float gridIncrement = maxLength / numberOfGridlines; 621 | List gridLinePositions = getGridLinePositions(gridSize); 622 | 623 | Rectangle selectedArea = new Rectangle (p.x,p.y, s.x,s.y); 624 | 625 | // now work out the scaling factor that'll be needed to work out 626 | // the positions of the pixels on the bitmap. 627 | float scalingFactor = getImage().width / getImageFrame().getWidth(); 628 | 629 | // now go through all the combinations of the two values. 630 | Set nativeCoords = new HashSet(); 631 | for (Float a : gridLinePositions) 632 | { 633 | for (Float b : gridLinePositions) 634 | { 635 | PVector nativeCoord = new PVector(a, b); 636 | PVector cartesianCoord = asCartesianCoords(nativeCoord); 637 | if (selectedArea.surrounds(cartesianCoord)) 638 | { 639 | if (isMasked(cartesianCoord, scalingFactor)) 640 | { 641 | nativeCoord.z = MASKED_PIXEL_BRIGHTNESS; // magic number 642 | nativeCoords.add(nativeCoord); 643 | } 644 | else 645 | { 646 | if (sampleSize >= 1.0) 647 | { 648 | float brightness = getPixelBrightness(cartesianCoord, sampleSize, scalingFactor); 649 | nativeCoord.z = brightness; 650 | } 651 | nativeCoords.add(nativeCoord); 652 | } 653 | } 654 | } 655 | } 656 | 657 | return nativeCoords; 658 | } 659 | 660 | protected PVector snapToGrid(PVector loose, float gridSize) 661 | { 662 | List pos = getGridLinePositions(gridSize); 663 | boolean higherupperFound = false; 664 | boolean lowerFound = false; 665 | 666 | float halfGrid = gridSize / 2.0; 667 | float x = loose.x; 668 | float y = loose.y; 669 | 670 | Float snappedX = null; 671 | Float snappedY = null; 672 | 673 | int i = 0; 674 | while ((snappedX == null || snappedY == null) && i < pos.size()) 675 | { 676 | float upperBound = pos.get(i)+halfGrid; 677 | float lowerBound = pos.get(i)-halfGrid; 678 | // println("pos:" +pos.get(i) + "half: "+halfGrid+ ", upper: "+ upperBound + ", lower: " + lowerBound); 679 | if (snappedX == null 680 | && x > lowerBound 681 | && x <= upperBound) 682 | { 683 | snappedX = pos.get(i); 684 | // println("snappedX:" + snappedX); 685 | } 686 | 687 | if (snappedY == null 688 | && y > lowerBound 689 | && y <= upperBound) 690 | { 691 | snappedY = pos.get(i); 692 | // println("snappedY:" + snappedY); 693 | } 694 | 695 | i++; 696 | } 697 | 698 | PVector snapped = new PVector((snappedX == null) ? 0.0 : snappedX, (snappedY == null) ? 0.0 : snappedY); 699 | // println("loose:" + loose); 700 | // println("snapped:" + snapped); 701 | return snapped; 702 | } 703 | 704 | protected List getGridLinePositions(float gridSize) 705 | { 706 | setGridSize(gridSize); 707 | return this.gridLinePositions; 708 | } 709 | 710 | private List generateGridLinePositions(float gridSize) 711 | { 712 | List glp = new ArrayList(); 713 | float maxLength = getMaxLength(); 714 | for (float i = gridSize; i <= maxLength; i+=gridSize) 715 | { 716 | glp.add(i); 717 | } 718 | return glp; 719 | } 720 | 721 | 722 | 723 | } 724 | -------------------------------------------------------------------------------- /MachineExecWindow.pde: -------------------------------------------------------------------------------- 1 | ControlFrameSimple addMachineExecControlFrame(String theName, int theWidth, int theHeight, int theX, int theY, int theColor ) { 2 | final Frame f = new Frame( theName ); 3 | final ControlFrameSimple p = new ControlFrameSimple( this, theWidth, theHeight, theColor ); 4 | 5 | f.add( p ); 6 | p.init(); 7 | f.setTitle(theName); 8 | f.setSize( p.w, p.h ); 9 | f.setLocation( theX, theY ); 10 | f.addWindowListener( new WindowAdapter() { 11 | @Override 12 | public void windowClosing(WindowEvent we) { 13 | p.dispose(); 14 | f.dispose(); 15 | } 16 | } 17 | ); 18 | f.setResizable( true ); 19 | f.setVisible( true ); 20 | // sleep a little bit to allow p to call setup. 21 | // otherwise a nullpointerexception might be caused. 22 | try { 23 | Thread.sleep( 100 ); 24 | } 25 | catch(Exception e) { 26 | } 27 | 28 | // set up controls 29 | Textfield filenameField = p.cp5().addTextfield("machineExec_execFilename",20,20,150,20) 30 | .setText(getStoreFilename()) 31 | .setLabel("Filename to execute from") 32 | .addListener( new ControlListener() { 33 | public void controlEvent( ControlEvent ev ) { 34 | machineExec_execFilename(ev.getController().getStringValue()); 35 | Textfield tf = p.cp5().get(Textfield.class, "machineExec_execFilename"); 36 | } 37 | }); 38 | 39 | 40 | Button submitButton = p.cp5().addButton("machineExec_submitExecFilenameWindow",0,180,20,60,20) 41 | .setLabel("Submit") 42 | .addListener( new ControlListener() { 43 | public void controlEvent( ControlEvent ev ) { 44 | p.cp5().get(Textfield.class, "machineExec_execFilename").submit(); 45 | p.cp5().get(Textfield.class, "machineExec_execFilename").setText(getStoreFilename()); 46 | } 47 | }); 48 | 49 | filenameField.setFocus(true); 50 | 51 | return p; 52 | } 53 | 54 | void machineExec_execFilename(String filename) { 55 | println("Filename event: "+ filename); 56 | if (filename != null 57 | && filename.length() <= 12 58 | && !"".equals(filename.trim())) { 59 | filename = filename.trim(); 60 | setStoreFilename(filename); 61 | sendMachineExecMode(); 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /MachineStoreWindow.pde: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------ 2 | Details about the "machine store" subwindow 3 | ------------------------------------------------------------------------*/ 4 | 5 | ControlFrameSimple addMachineStoreControlFrame(String theName, int theWidth, int theHeight, int theX, int theY, int theColor ) { 6 | final Frame f = new Frame( theName ); 7 | final ControlFrameSimple p = new ControlFrameSimple( this, theWidth, theHeight, theColor ); 8 | 9 | f.add( p ); 10 | p.init(); 11 | f.setTitle(theName); 12 | f.setSize( p.w, p.h ); 13 | f.setLocation( theX, theY ); 14 | f.addWindowListener( new WindowAdapter() { 15 | @Override 16 | public void windowClosing(WindowEvent we) { 17 | p.dispose(); 18 | f.dispose(); 19 | } 20 | } 21 | ); 22 | f.setResizable( true ); 23 | f.setVisible( true ); 24 | // sleep a little bit to allow p to call setup. 25 | // otherwise a nullpointerexception might be caused. 26 | try { 27 | Thread.sleep( 100 ); 28 | } 29 | catch(Exception e) { 30 | } 31 | 32 | // set up controls 33 | 34 | Textfield filenameField = p.cp5().addTextfield("machineStore_storeFilename",20,20,150,20) 35 | .setText(getStoreFilename()) 36 | .setLabel("Filename to store to") 37 | .addListener( new ControlListener() { 38 | public void controlEvent( ControlEvent ev ) { 39 | machineStore_storeFilename(ev.getController().getStringValue()); 40 | Textfield tf = p.cp5().get(Textfield.class, "machineExec_execFilename"); 41 | } 42 | }); 43 | 44 | Button submitButton = p.cp5().addButton("machineStore_submitStoreFilenameWindow",0,180,20,60,20) 45 | .setLabel("Submit") 46 | .addListener( new ControlListener() { 47 | public void controlEvent( ControlEvent ev ) { 48 | p.cp5().get(Textfield.class, "machineStore_storeFilename").submit(); 49 | p.cp5().get(Textfield.class, "machineStore_storeFilename").setText(getStoreFilename()); 50 | } 51 | }); 52 | 53 | Toggle overwriteToggle = p.cp5().addToggle("machineStore_toggleAppendToFile",true,180,50,20,20) 54 | .setCaptionLabel("Overwrite existing file") 55 | .plugTo(this, "machineStore_toggleAppendToFile"); 56 | 57 | 58 | filenameField.setFocus(true); 59 | 60 | return p; 61 | } 62 | 63 | void machineStore_toggleAppendToFile(boolean theFlag) { 64 | setOverwriteExistingStoreFile(theFlag); 65 | } 66 | 67 | void machineStore_storeFilename(String filename) { 68 | println("Filename event: "+ filename); 69 | if (filename != null 70 | && filename.length() <= 12 71 | && !"".equals(filename.trim())) { 72 | filename = filename.trim(); 73 | setStoreFilename(filename); 74 | sendMachineStoreMode(); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Misc.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | 30 | class Scaler 31 | { 32 | public float scale = 1.0; 33 | public float mmPerStep = 1.0; 34 | 35 | public Scaler(float scale, float mmPerStep) 36 | { 37 | this.scale = scale; 38 | this.mmPerStep = mmPerStep; 39 | } 40 | public void setScale(float scale) 41 | { 42 | this.scale = scale; 43 | } 44 | 45 | public float scale(float in) 46 | { 47 | return in * mmPerStep * scale; 48 | } 49 | } 50 | 51 | class PreviewVector extends PVector 52 | { 53 | public String command; 54 | } 55 | // 56 | // 57 | //import java.awt.Toolkit; 58 | //import java.awt.BorderLayout; 59 | //import java.awt.GraphicsEnvironment; 60 | // 61 | //public class Console extends WindowAdapter implements WindowListener, ActionListener, Runnable 62 | //{ 63 | // private JFrame frame; 64 | // private JTextArea textArea; 65 | // private Thread reader; 66 | // private Thread reader2; 67 | // private boolean quit; 68 | // 69 | // private final PipedInputStream pin=new PipedInputStream(); 70 | // private final PipedInputStream pin2=new PipedInputStream(); 71 | // 72 | // private PrintStream cOut = System.out; 73 | // private PrintStream cErr = System.err; 74 | // 75 | // Thread errorThrower; // just for testing (Throws an Exception at this Console 76 | // 77 | // public Console() 78 | // { 79 | // // create all components and add them 80 | // frame=new JFrame("Java Console"); 81 | // Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize(); 82 | // Dimension frameSize=new Dimension((int)(screenSize.width/2),(int)(screenSize.height/2)); 83 | // int x=(int)(frameSize.width/2); 84 | // int y=(int)(frameSize.height/2); 85 | // frame.setBounds(x,y,frameSize.width,frameSize.height); 86 | // 87 | // textArea=new JTextArea(); 88 | // textArea.setEditable(false); 89 | // JButton button=new JButton("clear"); 90 | // 91 | // frame.getContentPane().setLayout(new BorderLayout()); 92 | // frame.getContentPane().add(new JScrollPane(textArea),BorderLayout.CENTER); 93 | // frame.getContentPane().add(button,BorderLayout.SOUTH); 94 | // frame.setVisible(true); 95 | // 96 | // frame.addWindowListener(this); 97 | // button.addActionListener(this); 98 | // 99 | // try 100 | // { 101 | // this.cOut = System.out; 102 | // PipedOutputStream pout=new PipedOutputStream(this.pin); 103 | // System.setOut(new PrintStream(pout,true)); 104 | // } 105 | // catch (java.io.IOException io) 106 | // { 107 | // textArea.append("Couldn't redirect STDOUT to this console\n"+io.getMessage()); 108 | // } 109 | // catch (SecurityException se) 110 | // { 111 | // textArea.append("Couldn't redirect STDOUT to this console\n"+se.getMessage()); 112 | // } 113 | // 114 | // try 115 | // { 116 | // this.cErr = System.err; 117 | // PipedOutputStream pout2=new PipedOutputStream(this.pin2); 118 | // System.setErr(new PrintStream(pout2,true)); 119 | // } 120 | // catch (java.io.IOException io) 121 | // { 122 | // textArea.append("Couldn't redirect STDERR to this console\n"+io.getMessage()); 123 | // } 124 | // catch (SecurityException se) 125 | // { 126 | // textArea.append("Couldn't redirect STDERR to this console\n"+se.getMessage()); 127 | // } 128 | // 129 | // quit=false; // signals the Threads that they should exit 130 | // 131 | // // Starting two seperate threads to read from the PipedInputStreams 132 | // // 133 | // reader=new Thread(this); 134 | // reader.setDaemon(true); 135 | // reader.start(); 136 | // // 137 | // reader2=new Thread(this); 138 | // reader2.setDaemon(true); 139 | // reader2.start(); 140 | // 141 | //// // testing part 142 | //// // you may omit this part for your application 143 | //// // 144 | //// System.out.println("Hello World 2"); 145 | //// System.out.println("All fonts available to Graphic2D:\n"); 146 | //// GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 147 | //// String[] fontNames=ge.getAvailableFontFamilyNames(); 148 | //// for(int n=0;n. 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | class Panel 30 | { 31 | private Rectangle outline = null; 32 | private String name = null; 33 | private List controls = null; 34 | private Map controlPositions = null; 35 | private Map controlSizes = null; 36 | private boolean resizable = true; 37 | private float minimumHeight = DEFAULT_CONTROL_SIZE.y+4; 38 | private color outlineColour = color(255); 39 | 40 | public final color CONTROL_COL_BG_DEFAULT = color(0,54,82); 41 | public final color CONTROL_COL_BG_DISABLED = color(20,44,62); 42 | public final color CONTROL_COL_LABEL_DEFAULT = color(255); 43 | public final color CONTROL_COL_LABEL_DISABLED = color(200); 44 | 45 | public Panel(String name, Rectangle outline) 46 | { 47 | this.name = name; 48 | this.outline = outline; 49 | } 50 | 51 | public Rectangle getOutline() 52 | { 53 | return this.outline; 54 | } 55 | public void setOutline(Rectangle r) 56 | { 57 | this.outline = r; 58 | } 59 | 60 | public String getName() 61 | { 62 | return this.name; 63 | } 64 | public void setName(String name) 65 | { 66 | this.name = name; 67 | } 68 | 69 | public List getControls() 70 | { 71 | if (this.controls == null) 72 | this.controls = new ArrayList(0); 73 | return this.controls; 74 | } 75 | public void setControls(List c) 76 | { 77 | this.controls = c; 78 | } 79 | 80 | public Map getControlPositions() 81 | { 82 | return this.controlPositions; 83 | } 84 | public void setControlPositions(Map cp) 85 | { 86 | this.controlPositions = cp; 87 | } 88 | 89 | public Map getControlSizes() 90 | { 91 | return this.controlSizes; 92 | } 93 | public void setControlSizes(Map cs) 94 | { 95 | this.controlSizes = cs; 96 | } 97 | 98 | void setOutlineColour(color c) 99 | { 100 | this.outlineColour = c; 101 | } 102 | 103 | void setResizable(boolean r) 104 | { 105 | this.resizable = r; 106 | } 107 | boolean isResizable() 108 | { 109 | return this.resizable; 110 | } 111 | 112 | void setMinimumHeight(float h) 113 | { 114 | this.minimumHeight = h; 115 | } 116 | float getMinimumHeight() 117 | { 118 | return this.minimumHeight; 119 | } 120 | 121 | public void draw() 122 | { 123 | if (debugPanels) { 124 | stroke(outlineColour); 125 | strokeWeight(2); 126 | rect(getOutline().getLeft(), getOutline().getTop(), getOutline().getWidth(), getOutline().getHeight()); 127 | } 128 | 129 | drawControls(); 130 | } 131 | 132 | public void drawControls() 133 | { 134 | for (Controller c : this.getControls()) 135 | { 136 | PVector pos = getControlPositions().get(c.getName()); 137 | float x = pos.x+getOutline().getLeft(); 138 | float y = pos.y+getOutline().getTop(); 139 | c.setPosition(x, y); 140 | 141 | PVector cSize = getControlSizes().get(c.getName()); 142 | c.setSize((int)cSize.x, (int)cSize.y); 143 | 144 | boolean locked = false; 145 | 146 | // theres a few cases here where the controls are locked (disabled) 147 | 148 | // any drawing / extracting controls are disabled if there is no selec 149 | // box specified. 150 | if (getControlsToLockIfBoxNotSpecified().contains(c.getName()) && !isBoxSpecified()) 151 | { 152 | locked = true; 153 | } 154 | 155 | // if there is no vector shape loaded then lock the "draw vector" 156 | // control. 157 | if (c.getName().equals(MODE_RENDER_VECTORS) && getVectorShape() == null) 158 | { 159 | locked = true; 160 | } 161 | 162 | // if there's no image loaded, then hide resizing/moving 163 | if (getControlsToLockIfImageNotLoaded().contains(c.getName()) && getDisplayMachine().getImage() == null) 164 | { 165 | locked = true; 166 | } 167 | 168 | // if there's no vector loaded, then hide vector controls 169 | if (getControlsToLockIfVectorNotLoaded().contains(c.getName()) && vectorFilename == null) 170 | { 171 | locked = true; 172 | } 173 | 174 | 175 | if (c.getName().equals(MODE_LOAD_VECTOR_FILE)) 176 | { 177 | if (getVectorShape() != null) 178 | c.setLabel("Clear vector"); 179 | else 180 | c.setLabel("Load vector"); 181 | } 182 | else if (c.getName().equals(MODE_LOAD_IMAGE)) 183 | { 184 | if (getDisplayMachine().getImage() != null) 185 | c.setLabel("Clear image"); 186 | else 187 | c.setLabel("Load image file"); 188 | } 189 | 190 | 191 | int col = c.getColor().getBackground(); 192 | setLock(c, locked); 193 | } 194 | } 195 | 196 | void setLock(Controller c, boolean locked) 197 | { 198 | c.setLock(locked); 199 | if (locked) 200 | { 201 | c.setColorBackground(CONTROL_COL_BG_DISABLED); 202 | c.setColorLabel(CONTROL_COL_LABEL_DISABLED); 203 | } 204 | else 205 | { 206 | c.setColorBackground(CONTROL_COL_BG_DEFAULT); 207 | c.setColorLabel(CONTROL_COL_LABEL_DEFAULT); 208 | } 209 | } 210 | 211 | void setSizeByHeight(float h) 212 | { 213 | // println("Setting size for " + this.getName()); 214 | if (this.isResizable()) 215 | { 216 | if (h <= getMinimumHeight()) 217 | this.getOutline().setHeight(getMinimumHeight()); 218 | else 219 | this.getOutline().setHeight(h); 220 | 221 | setControlPositions(buildControlPositionsForPanel(this)); 222 | 223 | float left = 0.0; 224 | for (String key : getControlPositions().keySet()) 225 | { 226 | PVector pos = getControlPositions().get(key); 227 | if (pos.x > left) 228 | { 229 | left = pos.x; 230 | } 231 | } 232 | float right = left + DEFAULT_CONTROL_SIZE.x; 233 | this.getOutline().setWidth(right); 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /Polargraphsd spec.properties.txt: -------------------------------------------------------------------------------- 1 | # *** Polargraph properties file *** 2 | #Thu May 16 08:14:07 PDT 2013 3 | controller.pixel.samplearea=10.0 4 | controller.pictureframe.position.y=190 5 | controller.pictureframe.position.x=190 6 | controller.testPenWidth.startSize=0.5 7 | controller.machine.colour=969696 8 | machine.motors.mmPerRev=95.0 9 | controller.window.width=1190 10 | controller.frame.colour=C80000 11 | controller.image.position.y=178 12 | controller.image.position.x=178 13 | machine.motors.accel=400.0 14 | controller.image.height=180 15 | controller.machine.serialport=0 16 | controller.window.height=288 17 | controller.maxSegmentLength=2 18 | machine.penlift.up=90 19 | machine.penlift.down=180 20 | controller.page.position.y=120 21 | controller.vector.scaling=100.0 22 | controller.page.position.x=27 23 | controller.pictureframe.width=95 24 | machine.step.multiplier=1 25 | controller.grid.size=75.0 26 | controller.testPenWidth.endSize=2.0 27 | controller.pictureframe.height=95 28 | controller.page.colour=DCDCDC 29 | controller.testPenWidth.incrementSize=0.5 30 | controller.image.width=119 31 | machine.motors.stepsPerRev=800.0 32 | machine.pen.size=0.5 33 | controller.page.width=457 34 | controller.pixel.mask.color=00FF00 35 | controller.machine.baudrate=57600 36 | controller.vector.minLineLength=0 37 | machine.width=510 38 | controller.page.height=610 39 | controller.vector.position.y=0.0 40 | controller.background.colour=646464 41 | controller.image.filename=C\:\\Users\\mpoon@roblox.com\\Documents\\Mona_Lisa_resize.jpg 42 | controller.vector.position.x=0.0 43 | controller.homepoint.y=120.0 44 | controller.guide.colour=FFFFFF 45 | machine.motors.maxSpeed=600.0 46 | controller.homepoint.x=255.0 47 | controller.density.preview.style=1 48 | controller.pixel.scaling=1.0 49 | controller.densitypreview.colour=000000 50 | machine.height=730 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | polargraphcontroller 2 | ==================== 3 | 4 | Polargraph controller 5 | Copyright Sandy Noble 2018. 6 | 7 | - Requires the excellent ControlP5 GUI library available from https://github.com/sojamo/controlp5. 8 | - Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 9 | - Running on Processing v2.2.1. 10 | 11 | This is a desktop application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 12 | 13 | The [latest releases bundle] (https://github.com/euphy/polargraphcontroller/releases/latest) contains 14 | copies of all the libraries that I use, as well as all the source, and compiled versions of the code where sensible. 15 | 16 | How to [run it from source](https://github.com/euphy/polargraph/wiki/Running-the-controller-from-source-code). 17 | 18 | sandy.noble@gmail.com 19 | http://www.polargraph.co.uk/ 20 | -------------------------------------------------------------------------------- /Rectangle.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | class Rectangle 30 | { 31 | public PVector position = null; 32 | public PVector size = null; 33 | 34 | public Rectangle(float px, float py, float width, float height) 35 | { 36 | this.position = new PVector(px, py); 37 | this.size = new PVector(width, height); 38 | } 39 | public Rectangle(PVector position, PVector size) 40 | { 41 | this.position = position; 42 | this.size = size; 43 | } 44 | public Rectangle(Rectangle r) 45 | { 46 | this.position = new PVector(r.getPosition().x, r.getPosition().y); 47 | this.size = new PVector(r.getSize().x, r.getSize().y); 48 | } 49 | 50 | public float getWidth() 51 | { 52 | return this.size.x; 53 | } 54 | public void setWidth(float w) 55 | { 56 | this.size.x = w; 57 | } 58 | public float getHeight() 59 | { 60 | return this.size.y; 61 | } 62 | public void setHeight(float h) 63 | { 64 | this.size.y = h; 65 | } 66 | public PVector getPosition() 67 | { 68 | return this.position; 69 | } 70 | public PVector getSize() 71 | { 72 | return this.size; 73 | } 74 | public PVector getTopLeft() 75 | { 76 | return getPosition(); 77 | } 78 | public PVector getTopRight() 79 | { 80 | return new PVector(this.size.x+this.position.x, this.position.y); 81 | } 82 | public PVector getBotRight() 83 | { 84 | return PVector.add(this.position, this.size); 85 | } 86 | public float getLeft() 87 | { 88 | return getPosition().x; 89 | } 90 | public float getRight() 91 | { 92 | return getPosition().x + getSize().x; 93 | } 94 | public float getTop() 95 | { 96 | return getPosition().y; 97 | } 98 | public float getBottom() 99 | { 100 | return getPosition().y + getSize().y; 101 | } 102 | 103 | public void setPosition(float x, float y) 104 | { 105 | if (this.position == null) 106 | this.position = new PVector(x, y); 107 | else 108 | { 109 | this.position.x = x; 110 | this.position.y = y; 111 | } 112 | } 113 | 114 | public Boolean surrounds(PVector p) 115 | { 116 | if (p.x >= this.getLeft() 117 | && p.x < this.getRight() 118 | && p.y >= this.getTop() 119 | && p.y < this.getBottom()-1) 120 | return true; 121 | else 122 | return false; 123 | } 124 | 125 | public String toString() { 126 | return new StringBuffer().append("Rectangle pos: ").append(this.getPosition()).append(", size: ").append(this.getSize()).append(".").toString(); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /SerialPortWindow.pde: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------ 2 | Class and controllers on the "serial port" subwindow 3 | ------------------------------------------------------------------------*/ 4 | 5 | ControlFrameSimple addSerialPortControlFrame(String theName, int theWidth, int theHeight, int theX, int theY, int theColor ) { 6 | final Frame f = new Frame( theName ); 7 | final ControlFrameSimple p = new ControlFrameSimple( this, theWidth, theHeight, theColor ); 8 | 9 | f.add( p ); 10 | p.init(); 11 | f.setTitle(theName); 12 | f.setSize( p.w, p.h ); 13 | f.setLocation( theX, theY ); 14 | f.addWindowListener( new WindowAdapter() { 15 | @Override 16 | public void windowClosing(WindowEvent we) { 17 | p.dispose(); 18 | f.dispose(); 19 | } 20 | } 21 | ); 22 | f.setResizable( true ); 23 | f.setVisible( true ); 24 | // sleep a little bit to allow p to call setup. 25 | // otherwise a nullpointerexception might be caused. 26 | try { 27 | Thread.sleep( 100 ); 28 | } 29 | catch(Exception e) { 30 | } 31 | 32 | ScrollableList sl = p.cp5().addScrollableList("dropdown_serialPort") 33 | .setPosition(10, 10) 34 | .setSize(150, 450) 35 | .setBarHeight(20) 36 | .setItemHeight(16) 37 | .plugTo(this, "dropdown_serialPort"); 38 | 39 | sl.addItem("No serial connection", -1); 40 | 41 | String[] ports = Serial.list(); 42 | 43 | for (int i = 0; i < ports.length; i++) { 44 | println("Adding " + ports[i]); 45 | sl.addItem(ports[i], i); 46 | } 47 | 48 | int portNo = getSerialPortNumber(); 49 | println("portNo: " + portNo); 50 | if (portNo < 0 || portNo >= ports.length) 51 | portNo = -1; 52 | 53 | // set the value of the actual control 54 | sl.setValue(portNo); 55 | 56 | sl.setOpen(true); 57 | return p; 58 | } 59 | 60 | 61 | void dropdown_serialPort(int newSerialPort) 62 | { 63 | println("In dropdown_serialPort, newSerialPort: " + newSerialPort); 64 | 65 | // No serial in list is slot 0 in code because of list index 66 | // So shift port index by one 67 | newSerialPort -= 1; 68 | 69 | if (newSerialPort == -2) 70 | { 71 | } 72 | else if (newSerialPort == -1) { 73 | println("Disconnecting serial port."); 74 | useSerialPortConnection = false; 75 | if (myPort != null) 76 | { 77 | myPort.stop(); 78 | myPort = null; 79 | } 80 | drawbotReady = false; 81 | drawbotConnected = false; 82 | serialPortNumber = newSerialPort; 83 | } 84 | else if (newSerialPort != getSerialPortNumber()) { 85 | println("About to connect to serial port in slot " + newSerialPort); 86 | // Print a list of the serial ports, for debugging purposes: 87 | if (newSerialPort < Serial.list().length) { 88 | try { 89 | drawbotReady = false; 90 | drawbotConnected = false; 91 | if (myPort != null) { 92 | myPort.stop(); 93 | myPort = null; 94 | } 95 | 96 | if (getSerialPortNumber() >= 0) 97 | println("closing " + Serial.list()[getSerialPortNumber()]); 98 | 99 | serialPortNumber = newSerialPort; 100 | String portName = Serial.list()[serialPortNumber]; 101 | 102 | myPort = new Serial(this, portName, getBaudRate()); 103 | //read bytes into a buffer until you get a linefeed (ASCII 10): 104 | myPort.bufferUntil('\n'); 105 | useSerialPortConnection = true; 106 | println("Successfully connected to port " + portName); 107 | } 108 | catch (Exception e) { 109 | println("Attempting to connect to serial port in slot " + getSerialPortNumber() 110 | + " caused an exception: " + e.getMessage()); 111 | } 112 | } else { 113 | println("No serial ports found."); 114 | useSerialPortConnection = false; 115 | } 116 | } else { 117 | println("no serial port change."); 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /controlsActions.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | void button_mode_begin() 30 | { 31 | button_mode_clearQueue(); 32 | } 33 | void numberbox_mode_changeGridSize(float value) 34 | { 35 | setGridSize(value); 36 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 37 | { 38 | getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 39 | } 40 | } 41 | void numberbox_mode_changeSampleArea(float value) 42 | { 43 | setSampleArea(value); 44 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 45 | { 46 | getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 47 | } 48 | } 49 | void numberbox_mode_changePixelScaling(float value) 50 | { 51 | setPixelScalingOverGridSize(value); 52 | } 53 | void minitoggle_mode_showImage(boolean flag) 54 | { 55 | this.displayingImage = flag; 56 | } 57 | void minitoggle_mode_showVector(boolean flag) 58 | { 59 | this.displayingVector = flag; 60 | } 61 | void minitoggle_mode_showDensityPreview(boolean flag) 62 | { 63 | this.displayingDensityPreview = flag; 64 | } 65 | void minitoggle_mode_showQueuePreview(boolean flag) 66 | { 67 | this.displayingQueuePreview = flag; 68 | } 69 | void minitoggle_mode_showGuides(boolean flag) 70 | { 71 | this.displayingGuides = flag; 72 | } 73 | void unsetOtherToggles(String except) 74 | { 75 | for (String name : getAllControls().keySet()) 76 | { 77 | if (name.startsWith("toggle_")) 78 | { 79 | if (name.equals(except)) 80 | { 81 | // println("not resetting this one."); 82 | } 83 | else 84 | { 85 | getAllControls().get(name).setValue(0); 86 | } 87 | } 88 | } 89 | } 90 | void button_mode_penUp() 91 | { 92 | addToCommandQueue(CMD_PENUP + penLiftUpPosition +",END"); 93 | } 94 | void button_mode_penDown() 95 | { 96 | addToCommandQueue(CMD_PENDOWN + penLiftDownPosition +",END"); 97 | } 98 | void numberbox_mode_penUpPos(int value) 99 | { 100 | penLiftUpPosition = value; 101 | } 102 | void numberbox_mode_penDownPos(int value) 103 | { 104 | penLiftDownPosition = value; 105 | } 106 | void button_mode_sendPenliftRange() 107 | { 108 | addToCommandQueue(CMD_SETPENLIFTRANGE+penLiftDownPosition+","+penLiftUpPosition+",END"); 109 | } 110 | void button_mode_sendPenliftRangePersist() 111 | { 112 | addToCommandQueue(CMD_SETPENLIFTRANGE+penLiftDownPosition+","+penLiftUpPosition+",1,END"); 113 | } 114 | 115 | void numberbox_mode_liveBlurValue(int value) 116 | { 117 | if (value != blurValue) 118 | { 119 | blurValue = value; 120 | retraceShape = true; 121 | } 122 | } 123 | void numberbox_mode_liveSimplificationValue(int value) 124 | { 125 | if (value != liveSimplification) 126 | { 127 | liveSimplification = value; 128 | retraceShape = true; 129 | } 130 | } 131 | void numberbox_mode_livePosteriseValue(int value) 132 | { 133 | if (value != posterizeValue) 134 | { 135 | posterizeValue = value; 136 | retraceShape = true; 137 | } 138 | } 139 | void button_mode_liveCaptureFromLive() 140 | { 141 | trace_captureCurrentImage(); 142 | } 143 | void button_mode_liveClearCapture() 144 | { 145 | captureShape = null; 146 | } 147 | void button_mode_liveAddCaption() 148 | { 149 | 150 | } 151 | void numberbox_mode_vectorPathLengthHighPassCutoff(int value) 152 | { 153 | pathLengthHighPassCutoff = value; 154 | } 155 | 156 | void button_mode_liveConfirmDraw() 157 | { 158 | if (captureShape != null) 159 | { 160 | confirmedDraw = true; 161 | 162 | // work out scaling and position 163 | float scaling = getDisplayMachine().inMM(getDisplayMachine().getImageFrame().getWidth()) / captureShape.getWidth(); 164 | PVector position = new PVector(getDisplayMachine().inMM(getDisplayMachine().getImageFrame().getPosition().x), 165 | getDisplayMachine().inMM(getDisplayMachine().getImageFrame().getPosition().y)); 166 | 167 | int oldPolygonizer = polygonizer; 168 | polygonizer = RG.ADAPTATIVE; 169 | setupPolygonizer(); 170 | sendVectorShapes(captureShape, scaling, position, PATH_SORT_CENTRE_FIRST); 171 | button_mode_penUp(); 172 | 173 | // save shape as SVG 174 | trace_saveShape(captureShape); 175 | polygonizer = oldPolygonizer; 176 | setupPolygonizer(); 177 | } 178 | } 179 | void toggle_mode_showWebcamRawVideo(boolean flag) 180 | { 181 | // drawingLiveVideo = flag; 182 | } 183 | void toggle_mode_flipWebcam(boolean flag) 184 | { 185 | flipWebcamImage = flag; 186 | } 187 | void toggle_mode_rotateWebcam(boolean flag) 188 | { 189 | rotateWebcamImage = flag; 190 | } 191 | 192 | 193 | void toggle_mode_inputBoxTopLeft(boolean flag) 194 | { 195 | if (flag) 196 | { 197 | unsetOtherToggles(MODE_INPUT_BOX_TOP_LEFT); 198 | setMode(MODE_INPUT_BOX_TOP_LEFT); 199 | } 200 | else 201 | currentMode = ""; 202 | } 203 | void toggle_mode_inputBoxBotRight(boolean flag) 204 | { 205 | if (flag) 206 | { 207 | unsetOtherToggles(MODE_INPUT_BOX_BOT_RIGHT); 208 | setMode(MODE_INPUT_BOX_BOT_RIGHT); 209 | // unset topleft 210 | } 211 | else 212 | currentMode = ""; 213 | } 214 | void button_mode_drawOutlineBox() 215 | { 216 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 217 | sendOutlineOfBox(); 218 | } 219 | void button_mode_drawOutlineBoxRows() 220 | { 221 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 222 | { 223 | // get the pixels 224 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 225 | sendOutlineOfRows(pixels, DRAW_DIR_SE); 226 | } 227 | } 228 | void button_mode_drawShadeBoxRowsPixels() 229 | { 230 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 231 | { 232 | // get the pixels 233 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 234 | sendOutlineOfPixels(pixels); 235 | } 236 | } 237 | void toggle_mode_drawToPosition(boolean flag) 238 | { 239 | // unset other toggles 240 | if (flag) 241 | { 242 | unsetOtherToggles(MODE_DRAW_TO_POSITION); 243 | setMode(MODE_DRAW_TO_POSITION); 244 | } 245 | } 246 | void button_mode_renderSquarePixel() 247 | { 248 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 249 | { 250 | // get the pixels 251 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 252 | sendSquarePixels(pixels); 253 | } 254 | } 255 | void button_mode_renderSawPixel() 256 | { 257 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 258 | { 259 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 260 | sendSawtoothPixels(pixels); 261 | } 262 | } 263 | void button_mode_renderCirclePixel() 264 | { 265 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 266 | { 267 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 268 | sendCircularPixels(pixels); 269 | } 270 | } 271 | void button_mode_renderVectors() 272 | { 273 | // turn off vector view and turn queue preview on 274 | //minitoggle_mode_showVector(false); 275 | minitoggle_mode_showQueuePreview(true); 276 | println("here"); 277 | sendVectorShapes(); 278 | } 279 | 280 | void toggle_mode_setPosition(boolean flag) 281 | { 282 | if (flag) 283 | { 284 | unsetOtherToggles(MODE_SET_POSITION); 285 | setMode(MODE_SET_POSITION); 286 | } 287 | } 288 | 289 | void button_mode_returnToHome() 290 | { 291 | // lift pen 292 | button_mode_penUp(); 293 | PVector pgCoords = getDisplayMachine().asNativeCoords(getHomePoint()); 294 | sendMoveToNativePosition(false, pgCoords); 295 | } 296 | 297 | void button_mode_drawTestPattern() 298 | { 299 | sendTestPattern(); 300 | } 301 | 302 | void button_mode_drawGrid() 303 | { 304 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 305 | { 306 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 307 | sendGridOfBox(pixels); 308 | } 309 | } 310 | void button_mode_loadImage() 311 | { 312 | if (getDisplayMachine().getImage() == null) 313 | { 314 | loadImageWithFileChooser(); 315 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 316 | { 317 | getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 318 | } 319 | } 320 | else 321 | { 322 | getDisplayMachine().setImage(null); 323 | getDisplayMachine().setImageFilename(null); 324 | } 325 | } 326 | 327 | void button_mode_loadVectorFile() 328 | { 329 | if (getVectorShape() == null) 330 | { 331 | loadVectorWithFileChooser(); 332 | minitoggle_mode_showVector(true); 333 | } 334 | else 335 | { 336 | vectorShape = null; 337 | vectorFilename = null; 338 | } 339 | } 340 | 341 | void numberbox_mode_pixelBrightThreshold(float value) 342 | { 343 | pixelExtractBrightThreshold = (int) value; 344 | } 345 | void numberbox_mode_pixelDarkThreshold(float value) 346 | { 347 | pixelExtractDarkThreshold = (int) value; 348 | } 349 | 350 | void button_mode_pauseQueue() 351 | { 352 | } 353 | void button_mode_runQueue() 354 | { 355 | } 356 | void button_mode_clearQueue() 357 | { 358 | resetQueue(); 359 | } 360 | void button_mode_setPositionHome() 361 | { 362 | sendSetHomePosition(); 363 | } 364 | void button_mode_drawTestPenWidth() 365 | { 366 | sendTestPenWidth(); 367 | } 368 | void button_mode_renderScaledSquarePixels() 369 | { 370 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 371 | { 372 | // get the pixels 373 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 374 | sendScaledSquarePixels(pixels); 375 | } 376 | } 377 | void button_mode_renderSolidSquarePixels() 378 | { 379 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 380 | { 381 | // get the pixels 382 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 383 | sendSolidSquarePixels(pixels); 384 | } 385 | } 386 | void button_mode_renderScribblePixels() 387 | { 388 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 389 | { 390 | // get the pixels 391 | Set pixels = getDisplayMachine().extractNativePixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), sampleArea); 392 | sendScribblePixels(pixels); 393 | } 394 | } 395 | void button_mode_changeMachineSpec() 396 | { 397 | sendMachineSpec(); 398 | } 399 | void button_mode_requestMachineSize() 400 | { 401 | sendRequestMachineSize(); 402 | } 403 | void button_mode_resetMachine() 404 | { 405 | sendResetMachine(); 406 | } 407 | void button_mode_saveProperties() 408 | { 409 | savePropertiesFile(); 410 | // clear old properties. 411 | props = null; 412 | loadFromPropertiesFile(); 413 | } 414 | void button_mode_saveAsProperties() 415 | { 416 | saveNewPropertiesFileWithFileChooser(); 417 | } 418 | void button_mode_loadProperties() 419 | { 420 | loadNewPropertiesFilenameWithFileChooser(); 421 | } 422 | void toggle_mode_moveImage(boolean flag) 423 | { 424 | if (flag) 425 | { 426 | unsetOtherToggles(MODE_MOVE_IMAGE); 427 | setMode(MODE_MOVE_IMAGE); 428 | } 429 | else 430 | { 431 | setMode(""); 432 | } 433 | } 434 | 435 | void toggle_mode_chooseChromaKeyColour(boolean flag) 436 | { 437 | if (flag) 438 | { 439 | unsetOtherToggles(MODE_CHOOSE_CHROMA_KEY_COLOUR); 440 | setMode(MODE_CHOOSE_CHROMA_KEY_COLOUR); 441 | } 442 | else 443 | setMode(""); 444 | } 445 | 446 | void button_mode_convertBoxToPictureframe() 447 | { 448 | setPictureFrameDimensionsToBox(); 449 | } 450 | void button_mode_selectPictureframe() 451 | { 452 | setBoxToPictureframeDimensions(); 453 | } 454 | void button_mode_exportQueue() 455 | { 456 | exportQueueToFile(); 457 | } 458 | void button_mode_importQueue() 459 | { 460 | importQueueFromFile(); 461 | } 462 | void toggle_mode_drawDirect(boolean flag) 463 | { 464 | if (flag) 465 | { 466 | unsetOtherToggles(MODE_DRAW_DIRECT); 467 | setMode(MODE_DRAW_DIRECT); 468 | } 469 | } 470 | 471 | void numberbox_mode_resizeImage(float value) 472 | { 473 | float steps = getDisplayMachine().inSteps(value); 474 | Rectangle r = getDisplayMachine().getImageFrame(); 475 | float ratio = r.getHeight() / r.getWidth(); 476 | 477 | float oldSize = r.getSize().x; 478 | 479 | r.getSize().x = steps; 480 | r.getSize().y = steps * ratio; 481 | 482 | float difference = (r.getSize().x / 2.0)-(oldSize/2.0); 483 | r.getPosition().x -= difference; 484 | r.getPosition().y -= difference * ratio; 485 | 486 | if (getDisplayMachine().pixelsCanBeExtracted() && isBoxSpecified()) 487 | getDisplayMachine().extractPixelsFromArea(getBoxVector1(), getBoxVectorSize(), getGridSize(), getSampleArea()); 488 | } 489 | 490 | void numberbox_mode_resizeVector(float value) 491 | { 492 | if (getVectorShape() != null) 493 | { 494 | // get current size of vector in local coordinates 495 | PVector oldVectorSize = new PVector(getVectorShape().width, getVectorShape().height); 496 | oldVectorSize = PVector.mult(oldVectorSize, (vectorScaling/100)); 497 | // and current centre point of vector 498 | PVector oldCentroid = new PVector(oldVectorSize.x / 2.0, oldVectorSize.y / 2.0); 499 | 500 | // get newly scaled size of vector 501 | PVector newVectorSize = new PVector(getVectorShape().width, getVectorShape().height); 502 | newVectorSize = PVector.mult(newVectorSize, (value/100)); 503 | // and new centre point of vector 504 | PVector newCentroid = new PVector(newVectorSize.x / 2.0, newVectorSize.y / 2.0); 505 | 506 | // difference is current centre minus new centre 507 | PVector difference = PVector.sub(oldCentroid, newCentroid); 508 | 509 | // add difference onto vector position 510 | PVector newVectorPosition = PVector.add(vectorPosition, difference); 511 | vectorPosition = newVectorPosition; 512 | } 513 | 514 | vectorScaling = value; 515 | 516 | } 517 | void toggle_mode_moveVector(boolean flag) 518 | { 519 | // unset other toggles 520 | if (flag) 521 | { 522 | unsetOtherToggles(MODE_MOVE_VECTOR); 523 | setMode(MODE_MOVE_VECTOR); 524 | } 525 | else 526 | { 527 | setMode(""); 528 | } 529 | } 530 | 531 | void numberbox_mode_changeMachineWidth(float value) 532 | { 533 | clearBoxVectors(); 534 | float steps = getDisplayMachine().inSteps((int) value); 535 | getDisplayMachine().getSize().x = steps; 536 | getDisplayMachine().maxLength = null; 537 | } 538 | void numberbox_mode_changeMachineHeight(float value) 539 | { 540 | clearBoxVectors(); 541 | float steps = getDisplayMachine().inSteps((int) value); 542 | getDisplayMachine().getSize().y = steps; 543 | getDisplayMachine().maxLength = null; 544 | } 545 | void numberbox_mode_changeMMPerRev(float value) 546 | { 547 | clearBoxVectors(); 548 | getDisplayMachine().setMMPerRev(value); 549 | } 550 | void numberbox_mode_changeStepsPerRev(float value) 551 | { 552 | clearBoxVectors(); 553 | getDisplayMachine().setStepsPerRev(value); 554 | } 555 | void numberbox_mode_changeStepMultiplier(float value) 556 | { 557 | machineStepMultiplier = (int) value; 558 | } 559 | void numberbox_mode_changeMinVectorLineLength(float value) 560 | { 561 | minimumVectorLineLength = (int) value; 562 | } 563 | void numberbox_mode_changePageWidth(float value) 564 | { 565 | float steps = getDisplayMachine().inSteps((int) value); 566 | getDisplayMachine().getPage().setWidth(steps); 567 | } 568 | void numberbox_mode_changePageHeight(float value) 569 | { 570 | float steps = getDisplayMachine().inSteps((int) value); 571 | getDisplayMachine().getPage().setHeight(steps); 572 | } 573 | void numberbox_mode_changePageOffsetX(float value) 574 | { 575 | float steps = getDisplayMachine().inSteps((int) value); 576 | getDisplayMachine().getPage().getTopLeft().x = steps; 577 | } 578 | void numberbox_mode_changePageOffsetY(float value) 579 | { 580 | float steps = getDisplayMachine().inSteps((int) value); 581 | getDisplayMachine().getPage().getTopLeft().y = steps; 582 | } 583 | void button_mode_changePageOffsetXCentre() 584 | { 585 | float pageWidth = getDisplayMachine().getPage().getWidth(); 586 | float machineWidth = getDisplayMachine().getSize().x; 587 | float diff = (machineWidth - pageWidth) / 2.0; 588 | getDisplayMachine().getPage().getTopLeft().x = (int) diff; 589 | initialiseNumberboxValues(getAllControls()); 590 | } 591 | 592 | void numberbox_mode_changeHomePointX(float value) 593 | { 594 | float steps = getDisplayMachine().inSteps((int) value); 595 | getHomePoint().x = steps; 596 | } 597 | void numberbox_mode_changeHomePointY(float value) 598 | { 599 | float steps = getDisplayMachine().inSteps((int) value); 600 | getHomePoint().y = steps; 601 | } 602 | void button_mode_changeHomePointXCentre() 603 | { 604 | float halfWay = getDisplayMachine().getSize().x / 2.0; 605 | getHomePoint().x = (int) halfWay; 606 | getHomePoint().y = (int) getDisplayMachine().getPage().getTop(); 607 | initialiseNumberboxValues(getAllControls()); 608 | } 609 | 610 | 611 | void numberbox_mode_changePenWidth(float value) 612 | { 613 | currentPenWidth = Math.round(value*100.0)/100.0; 614 | } 615 | void button_mode_sendPenWidth() 616 | { 617 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 618 | DecimalFormat df = (DecimalFormat)nf; 619 | df.applyPattern("###.##"); 620 | addToRealtimeCommandQueue(CMD_SETPENWIDTH+df.format(currentPenWidth)+",END"); 621 | } 622 | 623 | void numberbox_mode_changePenTestStartWidth(float value) 624 | { 625 | testPenWidthStartSize = Math.round(value*100.0)/100.0; 626 | } 627 | void numberbox_mode_changePenTestEndWidth(float value) 628 | { 629 | testPenWidthEndSize = Math.round(value*100.0)/100.0; 630 | } 631 | void numberbox_mode_changePenTestIncrementSize(float value) 632 | { 633 | testPenWidthIncrementSize = Math.round(value*100.0)/100.0; 634 | } 635 | 636 | void numberbox_mode_changeMachineMaxSpeed(float value) 637 | { 638 | currentMachineMaxSpeed = Math.round(value*100.0)/100.0; 639 | } 640 | void numberbox_mode_changeMachineAcceleration(float value) 641 | { 642 | currentMachineAccel = Math.round(value*100.0)/100.0; 643 | } 644 | void button_mode_sendMachineSpeed() 645 | { 646 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 647 | DecimalFormat df = (DecimalFormat)nf; 648 | 649 | df.applyPattern("###.##"); 650 | addToRealtimeCommandQueue(CMD_SETMOTORSPEED+df.format(currentMachineMaxSpeed)+",END"); 651 | 652 | df.applyPattern("###.##"); 653 | addToRealtimeCommandQueue(CMD_SETMOTORACCEL+df.format(currentMachineAccel)+",END"); 654 | } 655 | 656 | void button_mode_sendMachineSpeedPersist() 657 | { 658 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 659 | DecimalFormat df = (DecimalFormat)nf; 660 | 661 | df.applyPattern("###.##"); 662 | addToCommandQueue(CMD_SETMOTORSPEED+df.format(currentMachineMaxSpeed)+",1,END"); 663 | 664 | df.applyPattern("###.##"); 665 | addToCommandQueue(CMD_SETMOTORACCEL+df.format(currentMachineAccel)+",1,END"); 666 | } 667 | 668 | void button_mode_sendRoveArea() 669 | { 670 | if (isBoxSpecified()) 671 | { 672 | addToCommandQueue(CMD_SET_ROVE_AREA+(long)boxVector1.x+","+(long)boxVector1.y+"," 673 | +(long)(boxVector2.x-boxVector1.x)+","+(long)(boxVector2.y-boxVector1.y)+",END"); 674 | } 675 | } 676 | 677 | void button_mode_selectRoveImageSource() 678 | { 679 | addToCommandQueue(CMD_SELECT_ROVE_SOURCE_IMAGE+",w1.pbm,END"); 680 | } 681 | void button_mode_startMarking() 682 | { 683 | // C47,,,END 684 | addToCommandQueue(CMD_RENDER_ROVE+",1,1,END"); 685 | } 686 | void button_mode_stopMarking() 687 | { 688 | addToCommandQueue(CMD_RENDER_ROVE+",0,0,END"); 689 | } 690 | 691 | void toggle_mode_sendStartText(boolean flag) 692 | { 693 | if (flag) 694 | { 695 | unsetOtherToggles(MODE_SEND_START_TEXT); 696 | setMode(MODE_SEND_START_TEXT); 697 | } 698 | else 699 | { 700 | setMode(""); 701 | } 702 | } 703 | 704 | void button_mode_startSwirling() 705 | { 706 | addToCommandQueue(CMD_SWIRLING+"1,END"); 707 | } 708 | void button_mode_stopSwirling() 709 | { 710 | addToCommandQueue(CMD_SWIRLING+"0,END"); 711 | } 712 | void setMode(String m) 713 | { 714 | lastMode = currentMode; 715 | currentMode = m; 716 | } 717 | void revertToLastMode() 718 | { 719 | currentMode = lastMode; 720 | } 721 | 722 | void button_mode_sendButtonActivate() 723 | { 724 | addToCommandQueue(CMD_ACTIVATE_MACHINE_BUTTON+",END"); 725 | } 726 | void button_mode_sendButtonDeactivate() 727 | { 728 | addToCommandQueue(CMD_DEACTIVATE_MACHINE_BUTTON+",END"); 729 | } 730 | 731 | void numberbox_mode_previewCordOffsetValue(int value) 732 | { 733 | previewCordOffset = value; 734 | previewQueue(true); 735 | } 736 | 737 | void dropdown_mode_cycleDensityPreviewStyle(int index) 738 | { 739 | println("In dropdown_mode_cycleDensityPreviewStyle"); 740 | densityPreviewStyle = index; 741 | println("Style: " + densityPreviewStyle); 742 | } 743 | 744 | void numberbox_mode_changeDensityPreviewPosterize(int value) { 745 | if (value < 1) value = 1; 746 | else if (value > 255) value = 255; 747 | 748 | densityPreviewPosterize = value; 749 | } 750 | 751 | void minitoggle_mode_previewPixelDensityRange(boolean flag) { 752 | previewPixelDensityRange = flag; 753 | println("previewPixelDensityRange: " + previewPixelDensityRange); 754 | } 755 | 756 | void numberbox_mode_changePolygonizerLength(float value) { 757 | polygonizerLength = value; 758 | setupPolygonizer(); 759 | } 760 | 761 | void numberbox_mode_changePolygonizerAdaptativeAngle(float value) { 762 | println("numberbox_mode_changePolygonizerAdaptativeAngle"); 763 | polygonizerAdaptativeAngle = value; 764 | setupPolygonizer(); 765 | } 766 | 767 | void dropdown_mode_changePolygonizer(int value) 768 | { 769 | polygonizer = value; 770 | setupPolygonizer(); 771 | } 772 | 773 | void dropdown_mode_changeMaskInvert(int value) 774 | { 775 | invertMaskMode = value; 776 | rebuildPixels(); 777 | } 778 | 779 | 780 | -------------------------------------------------------------------------------- /controlsActionsWindows.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | 30 | void button_mode_sendMachineLiveMode() { 31 | sendMachineLiveMode(); 32 | } 33 | 34 | String CHANGE_SERIAL_PORT_WINDOW_NAME = "changeSerialPortWindow"; 35 | String MACHINE_STORE_WINDOW_NAME = "chooseStoreFilenameWindow"; 36 | String MACHINE_EXEC_WINDOW_NAME = "chooseExecFilenameWindow"; 37 | String DRAW_PIXELS_WINDOW_NAME = "drawPixelsWindow"; 38 | String DRAW_WRITING_WINDOW_NAME = "drawWritingWindow"; 39 | 40 | void button_mode_serialPortDialog() { 41 | ControlFrameSimple cf = addSerialPortControlFrame("Serial Port", 200, 500, 20, 240, color( 100 ) ); 42 | } 43 | 44 | void button_mode_machineStoreDialog() { 45 | ControlFrameSimple cf = addMachineStoreControlFrame("Machine Store", 450, 250, 20, 240, color( 100 ) ); 46 | } 47 | 48 | void button_mode_machineExecDialog() { 49 | ControlFrameSimple cf = addMachineExecControlFrame("Machine Execute", 450, 250, 20, 240, color( 100 ) ); 50 | } 51 | 52 | void button_mode_drawPixelsDialog() { 53 | ControlFrameSimple cf = addDrawPixelsControlFrame("Render pixels", 450, 250, 20, 240, color( 100 ) ); 54 | } 55 | 56 | void button_mode_drawWritingDialog() { 57 | ControlFrameSimple cf = addSpriteWritingControlFrame("Sprite Writing", 450, 250, 20, 240, color( 100 ) ); 58 | } 59 | 60 | void button_mode_RandomSpriteDialog() { 61 | ControlFrameSimple cf = addRandomSpriteControlFrame("Random Sprite", 450, 250, 20, 240, color( 100 ) ); 62 | } 63 | 64 | void button_mode_drawNorwegianDialog() { 65 | ControlFrameSimple cf = addNorwegianPixelControlFrame("Norwegian Pixel", 450, 250, 20, 240, color( 100 ) ); 66 | } 67 | 68 | ///*------------------------------------------------------------------------ 69 | // Details about the "writing" subwindow 70 | //------------------------------------------------------------------------*/ 71 | 72 | String spriteWriting_textToWrite = ""; 73 | String spriteWriting_spriteFilePrefix = "sprite/let"; 74 | String spriteWriting_spriteFileSuffix = ".txt"; 75 | 76 | ControlFrameSimple addSpriteWritingControlFrame(String theName, int theWidth, int theHeight, int theX, int theY, int theColor ) { 77 | final Frame f = new Frame( theName ); 78 | final ControlFrameSimple p = new ControlFrameSimple( this, theWidth, theHeight, theColor ); 79 | 80 | f.add( p ); 81 | p.init(); 82 | f.setTitle(theName); 83 | f.setSize( p.w, p.h ); 84 | f.setLocation( theX, theY ); 85 | f.addWindowListener( new WindowAdapter() { 86 | @Override 87 | public void windowClosing(WindowEvent we) { 88 | p.dispose(); 89 | f.dispose(); 90 | cp5s.remove(DRAW_WRITING_WINDOW_NAME); 91 | } 92 | } 93 | ); 94 | f.setResizable( true ); 95 | f.setVisible( true ); 96 | // sleep a little bit to allow p to call setup. 97 | // otherwise a nullpointerexception might be caused. 98 | try { 99 | Thread.sleep( 100 ); 100 | } 101 | catch(Exception e) { 102 | } 103 | 104 | cp5s.put(DRAW_WRITING_WINDOW_NAME, p.cp5()); 105 | println(cp5s); 106 | 107 | // set up controls 108 | Textfield spriteFileField = p.cp5().addTextfield("spriteWriting_spriteFilePrefixField", 20, 20, 150, 20) 109 | .setText(spriteWriting_getSpriteFilePrefix()) 110 | .setLabel("File prefix") 111 | .plugTo(this, "spriteWriting_spriteFilePrefixField"); 112 | 113 | Textfield writingField = p.cp5().addTextfield("spriteWriting_textToWriteField", 20, 60, 400, 20) 114 | .setText(spriteWriting_getTextToWrite()) 115 | .setLabel("Text to write") 116 | .plugTo(this, "spriteWriting_textToWriteField"); 117 | 118 | Button importTextButton = p.cp5().addButton("spriteWriting_importTextButton", 0, 20, 100, 120, 20) 119 | .setLabel("Load text from file") 120 | .addListener( new ControlListener() { 121 | public void controlEvent( ControlEvent ev ) { 122 | spriteWriting_importTextButton(); 123 | } 124 | }); 125 | 126 | RadioButton rPos = p.cp5().addRadioButton("spriteWriting_radio_drawWritingDirection", 20, 140); 127 | rPos.add("South-east", DRAW_DIR_SE); 128 | rPos.activate("South-east"); 129 | rPos.plugTo(this, "spriteWriting_radio_drawWritingDirection"); 130 | 131 | Button submitButton = p.cp5.addButton("spriteWriting_submitWritingWindow", 0, 300, 100, 120, 20) 132 | .setLabel("Generate commands") 133 | .addListener( new ControlListener() { 134 | public void controlEvent( ControlEvent ev ) { 135 | spriteWriting_submitWritingWindow(p.cp5()); 136 | } 137 | }); 138 | 139 | 140 | return p; 141 | } 142 | 143 | 144 | 145 | void spriteWriting_spriteFilePrefixField(String value) { 146 | spriteWriting_spriteFilePrefix = value; 147 | } 148 | void spriteWriting_textToWriteField(String value) { 149 | spriteWriting_textToWrite = value; 150 | } 151 | String spriteWriting_getTextToWrite() { 152 | return spriteWriting_textToWrite; 153 | } 154 | String spriteWriting_getSpriteFilePrefix() { 155 | return spriteWriting_spriteFilePrefix; 156 | } 157 | String spriteWriting_getSpriteFileSuffix() { 158 | return spriteWriting_spriteFileSuffix; 159 | } 160 | 161 | void spriteWriting_importTextButton() { 162 | println("Text being imported!"); 163 | selectInput("Select the text file to load the text from:", 164 | "spriteWriting_importTextToWriteFromFile"); 165 | } 166 | 167 | public void spriteWriting_importTextToWriteFromFile(File selection) { 168 | if (selection != null) { 169 | String fp = selection.getAbsolutePath(); 170 | println("Input file: " + fp); 171 | List rows = java.util.Arrays.asList(loadStrings(fp)); 172 | StringBuilder sb = new StringBuilder(200); 173 | for (String row : rows) { 174 | sb.append(row); 175 | } 176 | spriteWriting_textToWriteField(sb.toString()); 177 | println("Completed text import, " + spriteWriting_getTextToWrite().length() + " characters found."); 178 | 179 | println("Text: " + spriteWriting_getTextToWrite()); 180 | 181 | println(cp5s); 182 | 183 | Textfield tf = cp5s.get(DRAW_WRITING_WINDOW_NAME).get(Textfield.class, "spriteWriting_textToWriteField"); 184 | if (spriteWriting_getTextToWrite() != null 185 | && !"".equals(spriteWriting_getTextToWrite().trim())) { 186 | tf.setText(spriteWriting_getTextToWrite()); 187 | tf.submit(); 188 | tf.setText(spriteWriting_getTextToWrite()); 189 | } 190 | } 191 | } 192 | 193 | void spriteWriting_submitWritingWindow(ControlP5 parent) 194 | { 195 | println("Write."); 196 | 197 | Textfield tf = parent.get(Textfield.class, "spriteWriting_spriteFilePrefixField"); 198 | tf.submit(); 199 | tf.setText(spriteWriting_getSpriteFilePrefix()); 200 | 201 | Textfield wf = parent.get(Textfield.class, "spriteWriting_textToWriteField"); 202 | wf.submit(); 203 | wf.setText(spriteWriting_getTextToWrite()); 204 | 205 | println("Start dir: " + renderStartDirection); 206 | println("Sprite file prefix: " + spriteWriting_spriteFilePrefix); 207 | println("Text: " + spriteWriting_textToWrite); 208 | 209 | for (int i=0; i 2 | 3 | 4 | 18 | 20 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /data/midsquare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /data/midsquares.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 61 | 68 | 75 | 82 | 89 | 96 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /data/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euphy/polargraphcontroller/9c224d039b44c974c653a345e239ec49ea9603e1/data/x.png -------------------------------------------------------------------------------- /data/y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euphy/polargraphcontroller/9c224d039b44c974c653a345e239ec49ea9603e1/data/y.png -------------------------------------------------------------------------------- /drawing.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | static final String CMD_CHANGELENGTH = "C01,"; 30 | static final String CMD_SETPENWIDTH = "C02,"; 31 | //static final String CMD_CHANGEMOTORSPEED = "C03,"; 32 | //static final String CMD_CHANGEMOTORACCEL = "C04,"; 33 | static final String CMD_DRAWPIXEL = "C05,"; 34 | static final String CMD_DRAWSCRIBBLEPIXEL = "C06,"; 35 | static final String CMD_DRAWRECT = "C07,"; 36 | static final String CMD_CHANGEDRAWINGDIRECTION = "C08,"; 37 | static final String CMD_SETPOSITION = "C09,"; 38 | static final String CMD_TESTPATTERN = "C10,"; 39 | static final String CMD_TESTPENWIDTHSQUARE = "C11,"; 40 | static final String CMD_TESTPENWIDTHSCRIBBLE = "C12,"; 41 | static final String CMD_PENDOWN = "C13,"; 42 | static final String CMD_PENUP = "C14,"; 43 | static final String CMD_DRAWSAWPIXEL = "C15,"; 44 | static final String CMD_DRAWROUNDPIXEL = "C16,"; 45 | static final String CMD_CHANGELENGTHDIRECT = "C17,"; 46 | static final String CMD_TXIMAGEBLOCK = "C18,"; 47 | static final String CMD_STARTROVE = "C19,"; 48 | static final String CMD_STOPROVE = "C20,"; 49 | static final String CMD_SET_ROVE_AREA = "C21,"; 50 | static final String CMD_LOADMAGEFILE = "C23,"; 51 | static final String CMD_CHANGEMACHINESIZE = "C24,"; 52 | static final String CMD_CHANGEMACHINENAME = "C25,"; 53 | static final String CMD_REQUESTMACHINESIZE = "C26,"; 54 | static final String CMD_RESETMACHINE = "C27,"; 55 | static final String CMD_DRAWDIRECTIONTEST = "C28,"; 56 | static final String CMD_CHANGEMACHINEMMPERREV = "C29,"; 57 | static final String CMD_CHANGEMACHINESTEPSPERREV = "C30,"; 58 | static final String CMD_SETMOTORSPEED = "C31,"; 59 | static final String CMD_SETMOTORACCEL = "C32,"; 60 | static final String CMD_MACHINE_MODE_STORE_COMMANDS = "C33,"; 61 | static final String CMD_MACHINE_MODE_EXEC_FROM_STORE = "C34,"; 62 | static final String CMD_MACHINE_MODE_LIVE = "C35,"; 63 | static final String CMD_RANDOM_DRAW = "C36,"; 64 | static final String CMD_SETMACHINESTEPMULTIPLIER = "C37,"; 65 | static final String CMD_START_TEXT = "C38,"; 66 | static final String CMD_DRAW_SPRITE = "C39,"; 67 | static final String CMD_CHANGELENGTH_RELATIVE = "C40,"; 68 | static final String CMD_SWIRLING = "C41,"; 69 | static final String CMD_DRAW_RANDOM_SPRITE = "C42,"; 70 | static final String CMD_DRAW_NORWEGIAN = "C43,"; 71 | static final String CMD_DRAW_NORWEGIAN_OUTLINE = "C44,"; 72 | static final String CMD_SETPENLIFTRANGE = "C45,"; 73 | static final String CMD_SELECT_ROVE_SOURCE_IMAGE = "C46"; 74 | static final String CMD_RENDER_ROVE = "C47"; 75 | 76 | static final String CMD_ACTIVATE_MACHINE_BUTTON = "C49"; 77 | static final String CMD_DEACTIVATE_MACHINE_BUTTON = "C50"; 78 | 79 | static final int PATH_SORT_NONE = 0; 80 | static final int PATH_SORT_MOST_POINTS_FIRST = 1; 81 | static final int PATH_SORT_GREATEST_AREA_FIRST = 2; 82 | static final int PATH_SORT_CENTRE_FIRST = 3; 83 | 84 | private PVector mouseVector = new PVector(0, 0); 85 | 86 | Comparator xAscending = new Comparator() 87 | { 88 | public int compare(Object p1, Object p2) 89 | { 90 | PVector a = (PVector) p1; 91 | PVector b = (PVector) p2; 92 | 93 | int xValue = new Float(a.x).compareTo(b.x); 94 | return xValue; 95 | } 96 | }; 97 | 98 | Comparator yAscending = new Comparator() 99 | { 100 | public int compare(Object p1, Object p2) 101 | { 102 | PVector a = (PVector) p1; 103 | PVector b = (PVector) p2; 104 | 105 | int yValue = new Float(a.y).compareTo(b.y); 106 | return yValue; 107 | } 108 | }; 109 | 110 | void sendResetMachine() 111 | { 112 | String command = CMD_RESETMACHINE + "END"; 113 | addToCommandQueue(command); 114 | } 115 | void sendRequestMachineSize() 116 | { 117 | String command = CMD_REQUESTMACHINESIZE + "END"; 118 | addToCommandQueue(command); 119 | } 120 | void sendMachineSpec() 121 | { 122 | // ask for input to get the new machine size 123 | String command = CMD_CHANGEMACHINESIZE+getDisplayMachine().inMM(getDisplayMachine().getWidth())+","+getDisplayMachine().inMM(getDisplayMachine().getHeight())+",END"; 124 | addToCommandQueue(command); 125 | command = CMD_CHANGEMACHINEMMPERREV+int(getDisplayMachine().getMMPerRev())+",END"; 126 | addToCommandQueue(command); 127 | command = CMD_CHANGEMACHINESTEPSPERREV+int(getDisplayMachine().getStepsPerRev())+",END"; 128 | addToCommandQueue(command); 129 | command = CMD_SETMACHINESTEPMULTIPLIER+machineStepMultiplier+",END"; 130 | addToCommandQueue(command); 131 | command = CMD_SETPENLIFTRANGE+penLiftDownPosition+","+penLiftUpPosition+",1,END"; 132 | addToCommandQueue(command); 133 | 134 | // speeds 135 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 136 | DecimalFormat df = (DecimalFormat)nf; 137 | df.applyPattern("###.##"); 138 | addToCommandQueue(CMD_SETMOTORSPEED+df.format(currentMachineMaxSpeed)+",1,END"); 139 | addToCommandQueue(CMD_SETMOTORACCEL+df.format(currentMachineAccel)+",1,END"); 140 | } 141 | 142 | public PVector getMouseVector() 143 | { 144 | if (mouseVector == null) 145 | { 146 | mouseVector = new PVector(0, 0); 147 | } 148 | 149 | mouseVector.x = mouseX; 150 | mouseVector.y = mouseY; 151 | return mouseVector; 152 | } 153 | 154 | // Uses the mouse position unless one is specified 155 | void sendMoveToPosition(boolean direct) 156 | { 157 | sendMoveToPosition(direct, getMouseVector()); 158 | } 159 | 160 | void sendMoveToPosition(boolean direct, PVector position) 161 | { 162 | String command = null; 163 | PVector p = getDisplayMachine().scaleToDisplayMachine(position); 164 | p = getDisplayMachine().inSteps(p); 165 | p = getDisplayMachine().asNativeCoords(p); 166 | sendMoveToNativePosition(direct, p); 167 | } 168 | 169 | void sendMoveToNativePosition(boolean direct, PVector p) 170 | { 171 | String command = null; 172 | if (direct) 173 | command = CMD_CHANGELENGTHDIRECT+int(p.x+0.5)+","+int(p.y+0.5)+","+getMaxSegmentLength()+",END"; 174 | else 175 | command = CMD_CHANGELENGTH+(int)p.x+","+(int)p.y+",END"; 176 | 177 | addToCommandQueue(command); 178 | } 179 | 180 | 181 | int getMaxSegmentLength() 182 | { 183 | return this.maxSegmentLength; 184 | } 185 | 186 | void sendTestPattern() 187 | { 188 | String command = CMD_DRAWDIRECTIONTEST+int(gridSize)+",6,END"; 189 | addToCommandQueue(command); 190 | } 191 | 192 | void sendTestPenWidth() 193 | { 194 | NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK); 195 | DecimalFormat df = (DecimalFormat)nf; 196 | df.applyPattern("##0.##"); 197 | StringBuilder sb = new StringBuilder(); 198 | sb.append(testPenWidthCommand) 199 | .append(int(gridSize)) 200 | .append(",") 201 | .append(df.format(testPenWidthStartSize)) 202 | .append(",") 203 | .append(df.format(testPenWidthEndSize)) 204 | .append(",") 205 | .append(df.format(testPenWidthIncrementSize)) 206 | .append(",END"); 207 | addToCommandQueue(sb.toString()); 208 | } 209 | 210 | void sendSetPosition() 211 | { 212 | PVector p = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); 213 | p = getDisplayMachine().convertToNative(p); 214 | p = getDisplayMachine().inSteps(p); 215 | 216 | String command = CMD_SETPOSITION+int(p.x+0.5)+","+int(p.y+0.5)+",END"; 217 | addToCommandQueue(command); 218 | } 219 | 220 | void sendStartTextAtPoint() 221 | { 222 | PVector p = getDisplayMachine().scaleToDisplayMachine(getMouseVector()); 223 | p = getDisplayMachine().convertToNative(p); 224 | p = getDisplayMachine().inSteps(p); 225 | 226 | String command = CMD_START_TEXT+(int)p.x+","+(int)p.y+","+gridSize+",2,END"; 227 | addToCommandQueue(command); 228 | } 229 | 230 | void sendSetHomePosition() 231 | { 232 | PVector pgCoords = getDisplayMachine().asNativeCoords(getHomePoint()); 233 | 234 | String command = CMD_SETPOSITION+int(pgCoords.x+0.5)+","+int(pgCoords.y+0.5)+",END"; 235 | addToCommandQueue(command); 236 | } 237 | 238 | int scaleDensity(int inDens, int inMax, int outMax) 239 | { 240 | float reducedDens = (float(inDens) / float(inMax)) * float(outMax); 241 | reducedDens = outMax-reducedDens; 242 | // println("inDens:"+inDens+", inMax:"+inMax+", outMax:"+outMax+", reduced:"+reducedDens); 243 | 244 | // round up if bigger than .5 245 | int result = int(reducedDens); 246 | if (reducedDens - (result) > 0.5) 247 | result ++; 248 | 249 | //result = outMax - result; 250 | return result; 251 | } 252 | 253 | SortedMap> divideIntoRows(Set pixels, int direction) 254 | { 255 | SortedMap> inRows = new TreeMap>(); 256 | 257 | for (PVector p : pixels) 258 | { 259 | Float row = p.x; 260 | if (direction == DRAW_DIR_SE || direction == DRAW_DIR_NW) 261 | row = p.y; 262 | 263 | if (!inRows.containsKey(row)) 264 | { 265 | inRows.put(row, new ArrayList()); 266 | } 267 | inRows.get(row).add(p); 268 | } 269 | return inRows; 270 | } 271 | 272 | PVector sortPixelsInRowsAlternating(SortedMap> inRows, int initialDirection, float maxPixelSize) 273 | { 274 | PVector startPoint = null; 275 | Comparator comp = null; 276 | boolean rowIsAlongXAxis = true; 277 | 278 | if (initialDirection == DRAW_DIR_SE || initialDirection == DRAW_DIR_NW) 279 | { 280 | rowIsAlongXAxis = true; 281 | comp = xAscending; 282 | } 283 | else 284 | { 285 | rowIsAlongXAxis = false; 286 | comp = yAscending; 287 | } 288 | 289 | // now sort each row, reversing the direction after each row 290 | boolean reverse = false; 291 | for (Float rowCoord : inRows.keySet()) 292 | { 293 | println("row: " + rowCoord); 294 | List row = inRows.get(rowCoord); 295 | 296 | if (reverse) 297 | { 298 | // reverse it (descending) 299 | Collections.sort(row, comp); 300 | Collections.reverse(row); 301 | // if (startPoint == null) 302 | // { 303 | // if (rowIsAlongXAxis) 304 | // startPoint = new PVector(row.get(0).x+(maxPixelSize/2.0), row.get(0).y); 305 | // else 306 | // startPoint = new PVector(row.get(0).x, row.get(0).y-(maxPixelSize/2.0)); 307 | // } 308 | reverse = false; 309 | } 310 | else 311 | { 312 | // sort row ascending 313 | Collections.sort(row, comp); 314 | // if (startPoint == null) 315 | // { 316 | // if (rowIsAlongXAxis) 317 | // startPoint = new PVector(row.get(0).x-(maxPixelSize/2.0), row.get(0).y); 318 | // else 319 | // startPoint = new PVector(row.get(0).x, row.get(0).y+(maxPixelSize/2.0)); 320 | // } 321 | reverse = true; 322 | } 323 | } 324 | return startPoint; 325 | } 326 | 327 | void sortPixelsInRows(SortedMap> inRows, int initialDirection) 328 | { 329 | PVector startPoint = null; 330 | Comparator comp = null; 331 | boolean rowIsAlongXAxis = true; 332 | 333 | if (initialDirection == DRAW_DIR_SE || initialDirection == DRAW_DIR_NW) 334 | { 335 | rowIsAlongXAxis = true; 336 | comp = xAscending; 337 | } 338 | else 339 | { 340 | rowIsAlongXAxis = false; 341 | comp = yAscending; 342 | } 343 | 344 | // now sort each row, reversing the direction after each row 345 | for (Float rowCoord : inRows.keySet()) 346 | { 347 | println("row: " + rowCoord); 348 | List row = inRows.get(rowCoord); 349 | // sort row ascending 350 | Collections.sort(row, comp); 351 | 352 | if (initialDirection == DRAW_DIR_NW || initialDirection == DRAW_DIR_NE) 353 | Collections.reverse(row); 354 | } 355 | } 356 | 357 | 358 | 359 | void sendPixels(Set pixels, String pixelCommand, int initialDirection, int startCorner, float maxPixelSize, boolean scaleSizeToDensity) 360 | { 361 | 362 | // sort it into a map of rows, keyed by y coordinate value 363 | SortedMap> inRows = divideIntoRows(pixels, initialDirection); 364 | 365 | sortPixelsInRowsAlternating(inRows, initialDirection, maxPixelSize); 366 | 367 | // that was easy. 368 | // load the queue 369 | // add a preamble 370 | 371 | // set the first direction 372 | int drawDirection = initialDirection; 373 | String changeDir = CMD_CHANGEDRAWINGDIRECTION+getPixelDirectionMode()+"," + drawDirection +",END"; 374 | addToCommandQueue(changeDir); 375 | 376 | // reverse the row sequence if the draw is starting from the bottom 377 | // and reverse the pixel sequence if it needs to be done (odd number of rows) 378 | boolean reversePixelSequence = false; 379 | List rowKeys = new ArrayList(); 380 | rowKeys.addAll(inRows.keySet()); 381 | Collections.sort(rowKeys); 382 | if (startCorner == DRAW_DIR_SE || startCorner == DRAW_DIR_SW) 383 | { 384 | Collections.reverse(rowKeys); 385 | if (rowKeys.size() % 2 == 0) 386 | reversePixelSequence = true; 387 | } 388 | 389 | // and move the pen to just next to the first pixel 390 | List firstRow = inRows.get(rowKeys.get(0)); 391 | 392 | PVector startPoint = firstRow.get(0); 393 | int startPointX = int(startPoint.x); 394 | int startPointY = int(startPoint.y); 395 | int halfSize = int(maxPixelSize/2.0); 396 | 397 | print("Dir:"); 398 | if (initialDirection == DRAW_DIR_SE) 399 | { 400 | startPointX-=halfSize; 401 | println("SE"); 402 | } 403 | else if (initialDirection == DRAW_DIR_SW) 404 | { 405 | startPointY-=halfSize; 406 | println("SW"); 407 | } 408 | else if (initialDirection == DRAW_DIR_NW) 409 | { 410 | startPointX-=halfSize; 411 | println("NW"); 412 | } 413 | else if (initialDirection == DRAW_DIR_NE) 414 | { 415 | startPointY-=halfSize; 416 | println("NE"); 417 | } 418 | 419 | if (startPoint != null) 420 | { 421 | String touchdown = CMD_CHANGELENGTH+int(startPointX)+","+int(startPointY)+",END"; 422 | addToCommandQueue(touchdown); 423 | addToCommandQueue(CMD_PENDOWN+"END"); 424 | } 425 | 426 | boolean penLifted = false; 427 | 428 | // so for each row 429 | for (Float key : rowKeys) 430 | { 431 | List row = inRows.get(key); 432 | if (reversePixelSequence) 433 | Collections.reverse(row); 434 | 435 | for (PVector v : row) 436 | { 437 | if (isHiddenPixel(v)) // check for masked pixels, 438 | { 439 | //println("It's outside the bright/dark threshold."); 440 | if (liftPenOnMaskedPixels) 441 | { 442 | if (!penLifted) // if the pen isn't already up 443 | { 444 | String raisePen = CMD_PENUP + "END"; 445 | addToCommandQueue(raisePen); 446 | penLifted = true; 447 | } 448 | else 449 | { 450 | // println("Pen is already lifted."); 451 | } 452 | // now convert to ints 453 | int inX = int(v.x); 454 | int inY = int(v.y); 455 | int pixelSize = int(maxPixelSize); 456 | // render a fully bright (255) pixel. 457 | String command = pixelCommand+inX+","+inY+","+int(pixelSize+0.5)+",255,END"; 458 | addToCommandQueue(command); 459 | } 460 | else 461 | { 462 | //println("liftPenOnMaskedPixels is not selected."); 463 | } 464 | // so this pixel doesn't get added to the queue. 465 | } 466 | else // pixel wasn't masked - render it up 467 | { 468 | // now convert to ints 469 | int inX = int(v.x); 470 | int inY = int(v.y); 471 | Integer density = int(v.z); 472 | int pixelSize = int(maxPixelSize); 473 | if (scaleSizeToDensity) 474 | { 475 | pixelSize = scaleDensity(density, 255, int(maxPixelSize)); 476 | density = 0; 477 | } 478 | int scaledPixelSize = int((pixelSize*getPixelScalingOverGridSize())+0.5); 479 | String command = pixelCommand+inX+","+inY+","+scaledPixelSize+","+density+",END"; 480 | 481 | // put the pen down if lifting over masked pixels is on 482 | if (liftPenOnMaskedPixels && penLifted) 483 | { 484 | // println("Pen down."); 485 | String lowerPen = CMD_PENDOWN + "END"; 486 | addToCommandQueue(lowerPen); 487 | penLifted = false; 488 | } 489 | addToCommandQueue(command); 490 | } 491 | } 492 | 493 | drawDirection = flipDrawDirection(drawDirection); 494 | String command = CMD_CHANGEDRAWINGDIRECTION+getPixelDirectionMode()+"," + drawDirection +",END"; 495 | addToCommandQueue(command); 496 | } 497 | 498 | addToCommandQueue(CMD_PENUP+"END"); 499 | numberOfPixelsTotal = commandQueue.size(); 500 | startPixelTimer(); 501 | } 502 | 503 | 504 | int flipDrawDirection(int curr) 505 | { 506 | if (curr == DRAW_DIR_SE) 507 | return DRAW_DIR_NW; 508 | else if (curr == DRAW_DIR_NW) 509 | return DRAW_DIR_SE; 510 | else if (curr == DRAW_DIR_NE) 511 | return DRAW_DIR_SW; 512 | else if (curr == DRAW_DIR_SW) 513 | return DRAW_DIR_NE; 514 | else return DRAW_DIR_SE; 515 | } 516 | 517 | 518 | int getPixelDirectionMode() 519 | { 520 | return pixelDirectionMode; 521 | } 522 | 523 | 524 | void sendSawtoothPixels(Set pixels) 525 | { 526 | sendPixels(pixels, CMD_DRAWSAWPIXEL, renderStartDirection, renderStartPosition, getGridSize(), false); 527 | } 528 | void sendCircularPixels(Set pixels) 529 | { 530 | sendPixels(pixels, CMD_DRAWROUNDPIXEL, renderStartDirection, renderStartPosition, getGridSize(), false); 531 | } 532 | 533 | void sendScaledSquarePixels(Set pixels) 534 | { 535 | sendPixels(pixels, CMD_DRAWPIXEL, renderStartDirection, renderStartPosition, getGridSize(), true); 536 | } 537 | 538 | void sendSolidSquarePixels(Set pixels) 539 | { 540 | for (PVector p : pixels) 541 | { 542 | if (p.z != MASKED_PIXEL_BRIGHTNESS) 543 | p.z = 0.0; 544 | } 545 | sendPixels(pixels, CMD_DRAWPIXEL, renderStartDirection, renderStartPosition, getGridSize(), false); 546 | } 547 | 548 | void sendSquarePixels(Set pixels) 549 | { 550 | sendPixels(pixels, CMD_DRAWPIXEL, renderStartDirection, renderStartPosition, getGridSize(), false); 551 | } 552 | 553 | void sendScribblePixels(Set pixels) 554 | { 555 | sendPixels(pixels, CMD_DRAWSCRIBBLEPIXEL, renderStartDirection, renderStartPosition, getGridSize(), false); 556 | } 557 | 558 | 559 | void sendOutlineOfPixels(Set pixels) 560 | { 561 | // sort it into a map of rows, keyed by y coordinate value 562 | SortedMap> inRows = divideIntoRows(pixels, DRAW_DIR_SE); 563 | 564 | sortPixelsInRowsAlternating(inRows, DRAW_DIR_SE, getGridSize()); 565 | 566 | float halfGrid = getGridSize() / 2.0; 567 | for (Float key : inRows.keySet()) 568 | { 569 | for (PVector p : inRows.get(key)) 570 | { 571 | PVector startPoint = new PVector(p.x-halfGrid, p.y-halfGrid); 572 | PVector endPoint = new PVector(p.x+halfGrid, p.y+halfGrid); 573 | String command = CMD_DRAWRECT + int(startPoint.x)+","+int(startPoint.y)+","+int(endPoint.x)+","+int(endPoint.y)+",END"; 574 | addToCommandQueue(command); 575 | } 576 | } 577 | } 578 | 579 | void sendOutlineOfRows(Set pixels, int drawDirection) 580 | { 581 | // sort it into a map of rows, keyed by y coordinate value 582 | SortedMap> inRows = divideIntoRows(pixels, drawDirection); 583 | 584 | sortPixelsInRows(inRows, drawDirection); 585 | 586 | PVector offset = new PVector(getGridSize() / 2.0, getGridSize() / 2.0); 587 | for (Float key : inRows.keySet()) 588 | { 589 | PVector startPoint = inRows.get(key).get(0); 590 | PVector endPoint = inRows.get(key).get(inRows.get(key).size()-1); 591 | 592 | if (drawDirection == DRAW_DIR_SE) 593 | { 594 | startPoint.sub(offset); 595 | endPoint.add(offset); 596 | } 597 | else if (drawDirection == DRAW_DIR_NW) 598 | { 599 | startPoint.add(offset); 600 | endPoint.sub(offset); 601 | } 602 | else if (drawDirection == DRAW_DIR_SW) 603 | { 604 | startPoint.add(offset); 605 | endPoint.sub(offset); 606 | } 607 | else if (drawDirection == DRAW_DIR_NW) 608 | { 609 | startPoint.add(offset); 610 | endPoint.sub(offset); 611 | } 612 | 613 | String command = CMD_DRAWRECT + int(startPoint.x)+","+int(startPoint.y)+","+int(endPoint.x)+","+int(endPoint.y)+",END"; 614 | addToCommandQueue(command); 615 | } 616 | } 617 | 618 | void sendGridOfBox(Set pixels) 619 | { 620 | sendOutlineOfRows(pixels, DRAW_DIR_SE); 621 | sendOutlineOfRows(pixels, DRAW_DIR_SW); 622 | } 623 | 624 | 625 | void sendOutlineOfBox() 626 | { 627 | // convert cartesian to native format 628 | PVector tl = getDisplayMachine().inSteps(getBoxVector1()); 629 | PVector br = getDisplayMachine().inSteps(getBoxVector2()); 630 | 631 | PVector tr = new PVector(br.x, tl.y); 632 | PVector bl = new PVector(tl.x, br.y); 633 | 634 | tl = getDisplayMachine().asNativeCoords(tl); 635 | tr = getDisplayMachine().asNativeCoords(tr); 636 | bl = getDisplayMachine().asNativeCoords(bl); 637 | br = getDisplayMachine().asNativeCoords(br); 638 | 639 | String cmd = (true) ? CMD_CHANGELENGTHDIRECT : CMD_CHANGELENGTH; 640 | 641 | String command = cmd+(int)tl.x+","+(int)tl.y+","+getMaxSegmentLength()+",END"; 642 | addToCommandQueue(command); 643 | 644 | command = cmd+(int)tr.x+","+(int)tr.y+","+getMaxSegmentLength()+",END"; 645 | addToCommandQueue(command); 646 | 647 | command = cmd+(int)br.x+","+(int)br.y+","+getMaxSegmentLength()+",END"; 648 | addToCommandQueue(command); 649 | 650 | command = cmd+(int)bl.x+","+(int)bl.y+","+getMaxSegmentLength()+",END"; 651 | addToCommandQueue(command); 652 | 653 | command = cmd+(int)tl.x+","+(int)tl.y+","+getMaxSegmentLength()+",END"; 654 | addToCommandQueue(command); 655 | } 656 | 657 | void sendVectorShapes() 658 | { 659 | sendVectorShapes(getVectorShape(), vectorScaling/100, getVectorPosition(), PATH_SORT_NONE); 660 | } 661 | 662 | 663 | void sendVectorShapes(RShape vec, float scaling, PVector position, int pathSortingAlgorithm) 664 | { 665 | println("Send vector shapes."); 666 | RPoint[][] pointPaths = vec.getPointsInPaths(); 667 | 668 | // sort the paths to optimise the draw sequence 669 | switch (pathSortingAlgorithm) { 670 | case PATH_SORT_MOST_POINTS_FIRST: pointPaths = sortPathsLongestFirst(pointPaths, pathLengthHighPassCutoff); break; 671 | case PATH_SORT_GREATEST_AREA_FIRST: pointPaths = sortPathsGreatestAreaFirst(vec, pathLengthHighPassCutoff); break; 672 | case PATH_SORT_CENTRE_FIRST: pointPaths = sortPathsCentreFirst(vec, pathLengthHighPassCutoff); break; 673 | } 674 | 675 | String command = ""; 676 | PVector lastPoint = new PVector(); 677 | boolean liftToGetToNewPoint = true; 678 | 679 | // go through and get each path 680 | for (int i = 0; i pathLengthHighPassCutoff) 686 | { 687 | List filteredPoints = filterPoints(pointPaths[i], VECTOR_FILTER_LOW_PASS, minimumVectorLineLength, scaling, position); 688 | if (!filteredPoints.isEmpty()) 689 | { 690 | // draw the first one with a pen up and down to get to it 691 | PVector p = filteredPoints.get(0); 692 | if ( p.x == lastPoint.x && p.y == lastPoint.y ) 693 | liftToGetToNewPoint = false; 694 | else 695 | liftToGetToNewPoint = true; 696 | 697 | // pen UP! (IF THE NEW POINT IS DIFFERENT FROM THE LAST ONE!) 698 | if (liftToGetToNewPoint) 699 | addToCommandQueue(CMD_PENUP+"END"); 700 | // move to this point and put the pen down 701 | command = CMD_CHANGELENGTHDIRECT+Math.round(p.x)+","+Math.round(p.y)+","+getMaxSegmentLength()+",END"; 702 | addToCommandQueue(command); 703 | if (liftToGetToNewPoint) 704 | addToCommandQueue(CMD_PENDOWN+"END"); 705 | 706 | 707 | 708 | // then just iterate through the rest 709 | for (int j=1; j pathsList = new ArrayList(pointPaths.length); 728 | for (int i = 0; i() { 738 | public int compare(RPoint[] o1, RPoint[] o2) { 739 | if (o1.length > o2.length) { 740 | return -1; 741 | } 742 | else if (o1.length < o2.length) { 743 | return 1; 744 | } 745 | else { 746 | return 0; 747 | } 748 | } 749 | } 750 | ); 751 | 752 | // filter out some short paths 753 | pathsList = removeShortPaths(pathsList, highPassCutoff); 754 | 755 | // and put them into a new array 756 | for (int i=0; i pathsList = new TreeMap(); 768 | 769 | int noOfChildren = vec.countChildren(); 770 | for (int i=0; i < noOfChildren; i++) 771 | { 772 | float area = vec.children[i].getArea(); 773 | RPoint[] path = vec.children[i].getPointsInPaths()[0]; 774 | pathsList.put(area, path); 775 | } 776 | 777 | RPoint[][] pointPaths = vec.getPointsInPaths(); 778 | List filtered = new ArrayList(); 779 | 780 | // and put them into a new array 781 | int i = 0; 782 | for (Float k : pathsList.keySet()) 783 | { 784 | if (k >= highPassCutoff) 785 | { 786 | filtered.add(pathsList.get(k)); 787 | println("Filtered kept path of area " + k); 788 | } 789 | else 790 | println("Filtered discarded path of area " + k); 791 | } 792 | 793 | pointPaths = new RPoint[filtered.size()][]; 794 | for (i = 0; i < filtered.size(); i++) 795 | { 796 | pointPaths[i] = filtered.get(i); 797 | } 798 | 799 | return pointPaths; 800 | } 801 | 802 | public RPoint[][] sortPathsCentreFirst(RShape vec, int highPassCutoff) 803 | { 804 | // put the paths into a list 805 | int noOfChildren = vec.countChildren(); 806 | List pathsList = new ArrayList(noOfChildren); 807 | for (int i=0; i < noOfChildren; i++) 808 | pathsList.add(vec.children[i]); 809 | List orderedPathsList = new ArrayList(noOfChildren); 810 | 811 | // make a tiny area in the centre of the shape, 812 | // plan to increment the size of the area until it covers vec entirely 813 | // (radius of area min = 0, max = distance from shape centre to any corner.) 814 | 815 | float aspectRatio = vec.getHeight() / vec.getWidth(); 816 | int n = 0; 817 | float w = 1.0; 818 | float h = w * aspectRatio; 819 | 820 | RPoint topLeft = vec.getTopLeft(); 821 | RPoint botRight = vec.getBottomRight(); 822 | 823 | PVector centre = new PVector(vec.getWidth()/2, vec.getHeight()/2); 824 | 825 | float vecWidth = vec.getWidth(); 826 | 827 | while (w <= vecWidth) 828 | { 829 | w+=6.0; 830 | h = w * aspectRatio; 831 | 832 | //println(n++ + ". Rect w " + w + ", h " + h); 833 | RShape field = RShape.createRectangle(centre.x-(w/2.0), centre.y-(h/2.0), w, h); 834 | // add all the shapes that are entirely inside the circle to orderedPathsList 835 | ListIterator it = pathsList.listIterator(); 836 | int shapesAdded = 0; 837 | while (it.hasNext()) 838 | { 839 | RShape sh = it.next(); 840 | if (field.contains(sh.getCenter())) 841 | { 842 | orderedPathsList.add(sh); 843 | // remove the shapes from pathsList (so it isn't found again) 844 | shapesAdded++; 845 | it.remove(); 846 | } 847 | } 848 | // increase the size of the circle and try again 849 | } 850 | 851 | RPoint[][] pointPaths = new RPoint[orderedPathsList.size()][];// vec.getPointsInPaths(); 852 | for (int i = 0; i < orderedPathsList.size(); i++) 853 | { 854 | pointPaths[i] = orderedPathsList.get(i).getPointsInPaths()[0]; 855 | } 856 | 857 | return pointPaths; 858 | } 859 | 860 | 861 | List removeShortPaths(List list, int cutoff) 862 | { 863 | if (cutoff > 0) 864 | { 865 | int numberOfPaths = list.size(); 866 | ListIterator it = list.listIterator(); 867 | while (it.hasNext ()) 868 | { 869 | RPoint[] paths = it.next(); 870 | if (paths == null || cutoff >= paths.length) 871 | { 872 | it.remove(); 873 | } 874 | } 875 | } 876 | return list; 877 | } 878 | 879 | List filterPoints(RPoint[] points, int filterToUse, long filterParam, float scaling, PVector position) 880 | { 881 | return filterPointsLowPass(points, filterParam, scaling, position); 882 | } 883 | 884 | List filterPointsLowPass(RPoint[] points, long filterParam, float scaling, PVector position) 885 | { 886 | List result = new ArrayList(); 887 | 888 | // scale and convert all the points first 889 | List scaled = new ArrayList(points.length); 890 | println("a filterPointsLowPass: Scaled length: " + points.length); 891 | for (int j = 0; j 1.0) 907 | { 908 | PVector p = scaled.get(0); 909 | result.add(p); 910 | 911 | for (int j = 1; j filterParam || diffy > filterParam) 919 | { 920 | println(j + ". Adding point " + p + " because diffx (" + diffx + ") or diffy (" + diffy + ") is > " + filterParam + ", last: " + result.get(result.size()-1)); 921 | result.add(p); 922 | } 923 | } 924 | } 925 | 926 | println("c filterPointsLowPass: Scaled length: " + result.size()); 927 | if (result.size() < 2) 928 | result.clear(); 929 | 930 | //println("finished filter."); 931 | return result; 932 | } 933 | 934 | 935 | 936 | 937 | void sendMachineStoreMode() 938 | { 939 | String overwrite = ",R"; 940 | if (!getOverwriteExistingStoreFile()) 941 | overwrite = ",A"; 942 | 943 | addToRealtimeCommandQueue(CMD_MACHINE_MODE_STORE_COMMANDS + getStoreFilename()+overwrite+",END"); 944 | } 945 | void sendMachineLiveMode() 946 | { 947 | addToCommandQueue(CMD_MACHINE_MODE_LIVE+"END"); 948 | } 949 | void sendMachineExecMode() 950 | { 951 | sendMachineLiveMode(); 952 | if (storeFilename != null && !"".equals(storeFilename)) 953 | addToCommandQueue(CMD_MACHINE_MODE_EXEC_FROM_STORE + getStoreFilename() + ",END"); 954 | } 955 | void sendRandomDraw() 956 | { 957 | addToCommandQueue(CMD_RANDOM_DRAW+"END"); 958 | } 959 | void sendStartSwirling() 960 | { 961 | addToCommandQueue(CMD_SWIRLING+"1,END"); 962 | } 963 | void sendStopSwirling() 964 | { 965 | addToCommandQueue(CMD_SWIRLING+"0,END"); 966 | } 967 | void sendDrawRandomSprite(String spriteFilename) 968 | { 969 | addToCommandQueue(CMD_DRAW_RANDOM_SPRITE+","+spriteFilename+",100,500,END"); 970 | } 971 | -------------------------------------------------------------------------------- /ikea.properties.txt: -------------------------------------------------------------------------------- 1 | # *** Polargraph properties file *** 2 | #Sun Apr 21 16:26:29 BST 2013 3 | controller.pixel.samplearea=10.0 4 | controller.pictureframe.position.y=161 5 | controller.pictureframe.position.x=124 6 | controller.testPenWidth.startSize=0.5 7 | controller.machine.colour=969696 8 | machine.motors.mmPerRev=95.0 9 | controller.window.width=1184 10 | controller.frame.colour=C80000 11 | controller.image.position.y=178 12 | controller.image.position.x=178 13 | machine.motors.accel=2012.0 14 | controller.image.height=119 15 | controller.machine.serialport=1 16 | controller.window.height=735 17 | controller.maxSegmentLength=2 18 | machine.penlift.up=191 19 | machine.penlift.down=97 20 | controller.page.position.y=120 21 | controller.vector.scaling=100.0 22 | controller.page.position.x=35 23 | controller.pictureframe.width=288 24 | machine.step.multiplier=8 25 | controller.grid.size=75.0 26 | controller.testPenWidth.endSize=2.0 27 | controller.pictureframe.height=319 28 | controller.page.colour=DCDCDC 29 | controller.testPenWidth.incrementSize=0.5 30 | controller.image.width=119 31 | machine.motors.stepsPerRev=400.0 32 | machine.pen.size=0.8 33 | controller.page.width=470 34 | controller.pixel.mask.color=00FF00 35 | controller.machine.baudrate=57600 36 | controller.vector.minLineLength=0 37 | machine.width=540 38 | controller.page.height=441 39 | controller.vector.position.y=0.0 40 | controller.background.colour=646464 41 | controller.image.filename= 42 | controller.vector.position.x=0.0 43 | controller.homepoint.y=120.0 44 | controller.guide.colour=FFFFFF 45 | machine.motors.maxSpeed=2438.0 46 | controller.homepoint.x=270.0 47 | controller.density.preview.style=1 48 | controller.pixel.scaling=1.0 49 | controller.densitypreview.colour=000000 50 | machine.height=570 51 | -------------------------------------------------------------------------------- /queue.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Tools for dealing with a full command queue. 3 | 4 | Optimise queue. 5 | */ 6 | 7 | 8 | -------------------------------------------------------------------------------- /tabSetup.pde: -------------------------------------------------------------------------------- 1 | /** 2 | Polargraph controller 3 | Copyright Sandy Noble 2015. 4 | 5 | This file is part of Polargraph Controller. 6 | 7 | Polargraph Controller is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Polargraph Controller is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Polargraph Controller. If not, see . 19 | 20 | Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/. 21 | Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/. 22 | 23 | This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link. 24 | 25 | sandy.noble@gmail.com 26 | http://www.polargraph.co.uk/ 27 | https://github.com/euphy/polargraphcontroller 28 | */ 29 | 30 | Set getPanelsForTab(String tabName) 31 | { 32 | if (getPanelsForTabs().containsKey(tabName)) 33 | { 34 | return getPanelsForTabs().get(tabName); 35 | } 36 | else 37 | return new HashSet(0); 38 | } 39 | 40 | Map> buildPanelsForTabs() 41 | { 42 | Map> map = new HashMap>(); 43 | 44 | Set inputPanels = new HashSet(2); 45 | inputPanels.add(getPanel(PANEL_NAME_INPUT)); 46 | inputPanels.add(getPanel(PANEL_NAME_GENERAL)); 47 | 48 | Set rovingPanels = new HashSet(2); 49 | rovingPanels.add(getPanel(PANEL_NAME_ROVING)); 50 | rovingPanels.add(getPanel(PANEL_NAME_GENERAL)); 51 | 52 | Set tracePanels = new HashSet(2); 53 | tracePanels.add(getPanel(PANEL_NAME_TRACE)); 54 | tracePanels.add(getPanel(PANEL_NAME_GENERAL)); 55 | 56 | Set detailsPanels = new HashSet(2); 57 | detailsPanels.add(getPanel(PANEL_NAME_DETAILS)); 58 | detailsPanels.add(getPanel(PANEL_NAME_GENERAL)); 59 | 60 | Set queuePanels = new HashSet(2); 61 | queuePanels.add(getPanel(PANEL_NAME_QUEUE)); 62 | queuePanels.add(getPanel(PANEL_NAME_GENERAL)); 63 | 64 | map.put(TAB_NAME_INPUT, inputPanels); 65 | map.put(TAB_NAME_ROVING, rovingPanels); 66 | map.put(TAB_NAME_TRACE, tracePanels); 67 | map.put(TAB_NAME_DETAILS, detailsPanels); 68 | map.put(TAB_NAME_QUEUE, queuePanels); 69 | 70 | return map; 71 | } 72 | 73 | List buildTabNames() 74 | { 75 | List list = new ArrayList(5); 76 | list.add(TAB_NAME_INPUT); 77 | list.add(TAB_NAME_ROVING); 78 | list.add(TAB_NAME_TRACE); 79 | list.add(TAB_NAME_DETAILS); 80 | list.add(TAB_NAME_QUEUE); 81 | return list; 82 | } 83 | 84 | void initTabs() 85 | { 86 | int tabWidth = (int)DEFAULT_CONTROL_SIZE.x; 87 | int tabHeight = (int)DEFAULT_CONTROL_SIZE.y; 88 | 89 | Tab.padding = 13; // that's a weird thing to do 90 | 91 | Tab input = cp5.getTab(TAB_NAME_INPUT); 92 | input.setLabel(TAB_LABEL_INPUT); 93 | input.activateEvent(true); 94 | input.setId(1); 95 | 96 | Tab details = cp5.getTab(TAB_NAME_DETAILS); 97 | details.setLabel(TAB_LABEL_DETAILS); 98 | details.activateEvent(true); 99 | details.setId(2); 100 | 101 | Tab roving = cp5.getTab(TAB_NAME_ROVING); 102 | roving.setLabel(TAB_LABEL_ROVING); 103 | roving.activateEvent(true); 104 | roving.setId(3); 105 | 106 | Tab trace = cp5.getTab(TAB_NAME_TRACE); 107 | trace.setLabel(TAB_LABEL_TRACE); 108 | trace.activateEvent(true); 109 | trace.setId(4); 110 | 111 | Tab queue = cp5.getTab(TAB_NAME_QUEUE); 112 | queue.setLabel(TAB_LABEL_QUEUE); 113 | queue.activateEvent(true); 114 | queue.setId(5); 115 | } 116 | 117 | public Set buildPanelNames() 118 | { 119 | Set set = new HashSet(6); 120 | set.add(PANEL_NAME_INPUT); 121 | set.add(PANEL_NAME_ROVING); 122 | set.add(PANEL_NAME_TRACE); 123 | set.add(PANEL_NAME_DETAILS); 124 | set.add(PANEL_NAME_QUEUE); 125 | set.add(PANEL_NAME_GENERAL); 126 | return set; 127 | } 128 | 129 | -------------------------------------------------------------------------------- /trace.pde: -------------------------------------------------------------------------------- 1 | public void trace_initTrace(PImage img) 2 | { 3 | // dummy initCamera(), does nothing 4 | // tracetraceEnabled = true; 5 | img.loadPixels(); 6 | blob_detector = new BlobDetector(img.width, img.height); 7 | blob_detector.setResolution(1); 8 | blob_detector.computeContours(true); 9 | blob_detector.computeBlobPixels(true); 10 | blob_detector.setMinMaxPixels(10*10, img.width * img.height); 11 | 12 | blob_detector.setBLOBable(new BLOBable_blueBlobs(liveImage)); 13 | } 14 | 15 | public void trace_initCameraProcCam() 16 | { 17 | // try 18 | // { 19 | // String[] cameras = Capture.list(); 20 | // if (cameras.length > 0) { 21 | // liveCamera = new Capture(this, 640, 480, cameras[0]); 22 | // //liveCamera.start(); 23 | // traceEnabled = true; 24 | // } 25 | // } 26 | // catch (Exception e) 27 | // { 28 | // println("Exception occurred trying to look for attached webcams. Webcam will not be used. " + e.getMessage()); 29 | // traceEnabled = false; 30 | // } 31 | 32 | } 33 | //public PImage trace_buildLiveImage() 34 | //{ 35 | // //liveCamera.start(); 36 | // PImage pimg = createImage(640, 480, RGB); 37 | // pimg.loadPixels(); 38 | // if (liveCamera.available()) { 39 | // liveCamera.read(); 40 | // } 41 | // pimg.pixels = liveCamera.pixels; 42 | // // flip the image left to right 43 | // if (flipWebcamImage) 44 | // { 45 | // 46 | // List list = new ArrayList(480); 47 | // 48 | // for (int r=0; r seps) 88 | { 89 | RShape allShapes = null; 90 | if (seps != null) 91 | { 92 | //println("detecting..."); 93 | int i = 0; 94 | int shapeNo = 1; 95 | allShapes = new RShape(); 96 | for (Integer key : seps.keySet()) 97 | { 98 | i++; 99 | //println("Analysing sep " + i + " of " + seps.size()); 100 | PImage sep = seps.get(key); 101 | blob_detector.setBLOBable(new BLOBable_blueBlobs(sep)); 102 | blob_detector.update(); 103 | ArrayList blob_list = blob_detector.getBlobs(); 104 | for (int blob_idx = 0; blob_idx < blob_list.size(); blob_idx++ ) { 105 | //println("Getting blob " + blob_idx + " of " + blob_list.size()); 106 | // get the current blob from the blob-list 107 | Blob blob = blob_list.get(blob_idx); 108 | // get the list of all the contours from the current blob 109 | ArrayList contour_list = blob.getContours(); 110 | // iterate through the contour_list 111 | for (int contour_idx = 0; contour_idx < contour_list.size(); contour_idx++ ) { 112 | // get the current contour from the contour-list 113 | Contour contour = contour_list.get(contour_idx); 114 | 115 | // example how to simplify a contour 116 | if (liveSimplification > 0) { 117 | // can improve speed, if the contour is needed for further work 118 | ArrayList contour_simple = Polyline.SIMPLIFY(contour, 2, 1); 119 | // repeat the simplifying process a view more times 120 | for (int simple_cnt = 0; simple_cnt < liveSimplification; simple_cnt++) { 121 | contour_simple= Polyline.SIMPLIFY(contour_simple, 2, simple_cnt); 122 | } 123 | RShape shp = trace_convertDiewaldToRShape(contour_simple); 124 | if (shp != null) 125 | { 126 | shapeNo++; 127 | //println("adding shape " + shapeNo + " - blob: " + blob_idx + ", contour: " + contour_idx); 128 | allShapes.addChild(shp); 129 | } 130 | } 131 | else 132 | { 133 | RShape shp = trace_convertDiewaldToRShape(contour.getPixels()); 134 | if (shp != null) 135 | allShapes.addChild(shp); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | // rotate image 142 | if (rotateWebcamImage) 143 | { 144 | allShapes.rotate(radians(-90)); 145 | // transform it so that top left is at 0,0. 146 | RPoint topLeft = allShapes.getTopLeft(); 147 | allShapes.translate(-topLeft.x, -topLeft.y); 148 | } 149 | return allShapes; 150 | } 151 | 152 | Map trace_buildSeps(PImage img, Integer keyColour) 153 | { 154 | // create separations 155 | // pull out number of colours 156 | Set colours = null; 157 | List colourList = null; 158 | 159 | colours = new HashSet(); 160 | for (int i=0; i< img.pixels.length; i++) { 161 | colours.add(img.pixels[i]); 162 | } 163 | colourList = new ArrayList(colours); 164 | 165 | Map seps = new HashMap(colours.size()); 166 | for (Integer colour : colours) { 167 | PImage sep = createImage(img.width, img.height, RGB); 168 | sep.loadPixels(); 169 | seps.put(colour, sep); 170 | } 171 | 172 | for (int i = 0; i points) 181 | { 182 | RShape shp = null; 183 | if (points.size() > 2) { 184 | shp = new RShape(); 185 | Pixel p = points.get(0); 186 | shp.addMoveTo(float(p.x_), float(p.y_)); 187 | for (int idx = 1; idx < points.size(); idx++) { 188 | p = points.get(idx); 189 | shp.addLineTo(float(p.x_), float(p.y_)); 190 | } 191 | shp.addClose(); 192 | } 193 | return shp; 194 | } 195 | 196 | 197 | public void trace_captureCurrentImage(PImage inImage) 198 | { 199 | captureShape = traceShape; 200 | } 201 | 202 | public void trace_captureCurrentImage() 203 | { 204 | // capturedImage = webcam_buildLiveImage(); 205 | if (getDisplayMachine().imageIsReady()) 206 | trace_captureCurrentImage(getDisplayMachine().getImage()); 207 | } 208 | 209 | public void trace_processLoadedImage() 210 | { 211 | trace_captureCurrentImage(getDisplayMachine().getImage()); 212 | } 213 | 214 | public void trace_saveShape(RShape sh) 215 | { 216 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss"); 217 | String dateCode = sdf.format(new java.util.Date()); 218 | String filename = shapeSavePath + shapeSavePrefix + dateCode + shapeSaveExtension; 219 | RG.saveShape(filename, sh); 220 | } 221 | 222 | //public void stop() { 223 | // liveCamera.stop(); 224 | // super.stop(); 225 | //} 226 | 227 | --------------------------------------------------------------------------------