├── .gitignore ├── LICENSE ├── README.md └── app ├── .vscode └── tasks.json ├── Sketch.pde ├── app.pde └── config.pde /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /app/out 3 | /app/output 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plotter-canvas 2 | 3 | An empty canvas for building plotter art in Processing. 4 | 5 | This projects runs full screen (optional) with a proportional canvas showing your sketch in context of the target print size. Output your sketch as plot-ready SVG with optional PNG preview. 6 | 7 | ## Getting Started 8 | 9 | ### 📺 [Video Walkthrough](https://youtu.be/P2iipcsJoCA) 10 | 11 | ### Requirements 12 | 13 | - [Processing 4](https://processing.org/download) 14 | 15 | ### Setup 16 | 17 | Put your sketch code in the `Sketch.pde` file. 18 | 19 | The main app will call your sketch's `draw` method once per frame. 20 | 21 | #### Config 22 | 23 | Set your paper size, pen thickness, and display scaling in the `config.pde` file. 24 | 25 | ## Usage 26 | 27 | Any drawing commands in your sketch's `draw` method will be output to SVG when saving. 28 | 29 | Use the `w`, `h`, and `ppi` variables in to your sketch to position your drawing on the canvas. 30 | 31 | ### Mouse & Keyboard Input 32 | 33 | You can use these Processing functions in your sketch to access mouse and keyboard input: 34 | [`mousePressed()`](https://processing.org/reference/mousePressed_.html) 35 | [`mouseReleased()`](https://processing.org/reference/mouseReleased_.html) 36 | [`keyPressed()`](https://processing.org/reference/keyPressed_.html) 37 | [`keyReleased()`](https://processing.org/reference/keyReleased_.html) 38 | 39 | Use `canvasMouseX` and `canvasMouseY` to get mouse coordinates relative to the canvas. 40 | 41 | ### Responding to Canvas Size Changes 42 | 43 | If you need to do something in your project to respond to a change in canvas size, you can implement the `setDimensions` function: 44 | 45 | ```java 46 | void setDimensions(int _w, int _h, float _ppi, float _strokeWeight) { 47 | super.setDimensions(_w, _h, _ppi, _strokeWeight); // you must call super 48 | 49 | // your code here... 50 | } 51 | ``` 52 | 53 | This function will be called by `app.pde` any time the canvas size changes. Be sure to call super in order to have the parent class correctly update the canvas values. 54 | 55 | ### Key Commands 56 | 57 | **`s`** : Save a plot-ready SVG. This also saves a PNG preview image if you have that flag set in the config file. 58 | 59 | **`g`** : Toggle a visual grid that represents the maximum plot area. 60 | 61 | ## Support 62 | 63 | This is a personal project and is mostly unsupported, but I'm happy to hear feedback or answer questions. 64 | 65 | ## License 66 | 67 | This project is licensed under the Unlicense - see the [LICENSE](LICENSE) file for details. 68 | 69 | --- 70 | 71 | 👨🏻‍🦲❤️🛠 72 | -------------------------------------------------------------------------------- /app/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run Sketch", 6 | "type": "shell", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "command": "${config:processing.path}-4", 12 | "presentation": { 13 | "echo": true, 14 | "reveal": "always", 15 | "focus": false, 16 | "panel": "dedicated" 17 | }, 18 | "args": [ 19 | "--force", 20 | "--sketch=${workspaceRoot}", 21 | "--output=${workspaceRoot}/out", 22 | "--run" 23 | ], 24 | "windows": { 25 | "type": "process", 26 | "args": [ 27 | "--force", 28 | { 29 | "value": "--sketch=${workspaceRoot}", 30 | "quoting": "strong" 31 | }, 32 | { 33 | "value": "--output=${workspaceRoot}\\out", 34 | "quoting": "strong" 35 | }, 36 | "--run" 37 | ] 38 | } 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /app/Sketch.pde: -------------------------------------------------------------------------------- 1 | class Sketch extends PlotterCanvas { 2 | // vars available in this Sketch: 3 | 4 | // int w, h; -- width & height in pixels 5 | // float screenScale, ppi; -- the scale factor and pixels per inch of current canvas 6 | // float strokeWeight; -- the scaled stroke weight for the sketch 7 | // int canvasMouseX, canvasMouseY; -- mouse position in canvas coordinates 8 | 9 | Sketch(){} 10 | 11 | void draw() { 12 | // replace this with your custom sketch drawing code 13 | rect((w - ppi) / 2, (h - ppi) / 2, ppi, ppi); 14 | } 15 | 16 | void mousePressed() { 17 | // mousePressed events get forwarded from main applet 18 | // use canvasMouseX and canvasMouseY to get canvas coordinates 19 | } 20 | 21 | // you can use these common functions too 22 | // void mouseReleased() {} 23 | // void keyPressed() {} 24 | // void keyReleased() {} 25 | 26 | // TODO: move this to the README docs 27 | // this gets called anytime canvas dimensions change 28 | // uncomment if you need to respond to size changes 29 | // void setDimensions(int _w, int _h, float _ppi, float _strokeWeight) { 30 | // super.setDimensions(_w, _h, _ppi, _strokeWeight); // you must call super 31 | // } 32 | 33 | } -------------------------------------------------------------------------------- /app/app.pde: -------------------------------------------------------------------------------- 1 | import processing.svg.*; 2 | 3 | int canvasXPx = 0; 4 | int canvasYPx = 0; 5 | int canvasWPx = 0; 6 | int canvasHPx = 0; 7 | 8 | float maxScreenScale; 9 | float screenScale; 10 | float penSizeMM; 11 | float printWInches; 12 | float printHInches; 13 | int printResolution = 144; 14 | float marginInches = 0; 15 | float strokeWeight; 16 | 17 | Sketch sketch; 18 | ImageSaver imgSaver; 19 | 20 | GraphPaper grid; 21 | boolean showGrid = false; 22 | boolean showUI = false; 23 | 24 | int plotWPx; 25 | int plotHPx; 26 | int plotXPx; 27 | int plotYPx; 28 | 29 | float plotWInches; 30 | float plotHInches; 31 | 32 | float maxPlotWInches; 33 | float maxPlotHInches; 34 | 35 | int canvasMouseX = 0; 36 | int canvasMouseY = 0; 37 | 38 | Field printWField; 39 | Field printHField; 40 | Field focusedField; 41 | String fieldStringVal = ""; 42 | 43 | void settings() { 44 | 45 | if(fullscreen) { 46 | fullScreen(); 47 | } else { 48 | size(displayWidth, displayHeight - 45); 49 | // size(1920, 1080); 50 | } 51 | if(useRetinaDisplay){ 52 | pixelDensity(displayDensity()); 53 | } 54 | } 55 | 56 | void setup() { 57 | maxScreenScale = displayScale * 300 / printResolution; 58 | penSizeMM = penSize; 59 | printWInches = printW; 60 | printHInches = printH; 61 | 62 | maxPlotWInches = maxPlotW; 63 | maxPlotHInches = maxPlotH; 64 | 65 | sketch = new Sketch(); 66 | updateKeyDimensions(); 67 | 68 | imgSaver = new ImageSaver(); 69 | imgSaver.savePNG = savePNGPreview; 70 | 71 | printWField = new Field(10, 10, printWInches, "W"); 72 | printHField = new Field(72, 10, printHInches, "H"); 73 | } 74 | 75 | 76 | void updateKeyDimensions() { 77 | calculateScreenScale(); 78 | calculatePlotArea(); 79 | strokeWeight = calculateStrokeSize(); 80 | 81 | println("page size: " + printWInches + " ✕ " + printHInches + " inches"); 82 | println("scaled to: " + canvasWPx + " ✕ " + canvasHPx + " pixels (" + screenScale + " scale)\n "); 83 | 84 | grid = new GraphPaper(maxPlotWInches, maxPlotHInches, printWInches, printHInches, printResolution * screenScale); 85 | if(sketch != null){ 86 | sketch.setDimensions(plotWPx, plotHPx, screenScale, printResolution * screenScale, strokeWeight); 87 | } 88 | } 89 | 90 | 91 | void draw() { 92 | drawBG(); 93 | if(showUI) drawUI(); 94 | translate(canvasXPx, canvasYPx); 95 | 96 | if(showGrid) grid.draw(); 97 | 98 | translate(plotXPx, plotYPx); 99 | imgSaver.startSave(); 100 | strokeWeight(strokeWeight); 101 | 102 | stroke(0); 103 | noFill(); 104 | sketch.draw(); 105 | 106 | imgSaver.endSave(get(), canvasXPx, canvasYPx, canvasWPx, canvasHPx); 107 | } 108 | 109 | float calculateStrokeSize() { 110 | // 1mm = 0.03937008 inches 111 | float size = (penSizeMM * 0.03937008) * printResolution * screenScale; 112 | return size; 113 | } 114 | 115 | void calculateScreenScale() { 116 | float maxW = width - 100; 117 | float maxH = height - 100; 118 | 119 | float _printWPx = printWInches * printResolution; 120 | float _printHPx = printHInches * printResolution; 121 | 122 | screenScale = maxW / _printWPx; 123 | 124 | if(_printHPx * screenScale > maxH){ 125 | screenScale = maxH / _printHPx; 126 | } 127 | 128 | if(screenScale > maxScreenScale){ 129 | screenScale = maxScreenScale; 130 | } 131 | 132 | canvasWPx = int(printWInches * printResolution * screenScale); 133 | canvasHPx = int(printHInches * printResolution * screenScale); 134 | 135 | canvasXPx = (width - canvasWPx) /2; 136 | canvasYPx = (height - canvasHPx) /2; 137 | } 138 | 139 | void calculatePlotArea() { 140 | if(constrainToPlotArea){ 141 | float mpw = maxPlotWInches; 142 | float mph = maxPlotHInches; 143 | if(printWInches < printHInches){ 144 | // portrait 145 | mpw = maxPlotHInches; 146 | mph = maxPlotWInches; 147 | } 148 | 149 | plotWInches = min(printWInches, mpw); 150 | plotHInches = min(printHInches, mph); 151 | 152 | plotWPx = int(plotWInches * printResolution * screenScale); 153 | plotHPx = int(plotHInches * printResolution * screenScale); 154 | 155 | plotXPx = (canvasWPx - plotWPx) / 2; 156 | plotYPx = (canvasHPx - plotHPx) / 2; 157 | } else { 158 | plotWPx = canvasWPx; 159 | plotHPx = canvasHPx; 160 | 161 | plotWInches = printWInches; 162 | plotHInches = printHInches; 163 | 164 | plotXPx = 0; 165 | plotYPx = 0; 166 | } 167 | } 168 | 169 | void drawPaperBG() { 170 | stroke(80); 171 | strokeWeight(4); 172 | rect(canvasXPx, canvasYPx, canvasWPx, canvasHPx); 173 | 174 | fill(255); 175 | noStroke(); 176 | rect(canvasXPx, canvasYPx, canvasWPx, canvasHPx); 177 | } 178 | 179 | void drawBG() { 180 | background(100); 181 | if(imgSaver.isBusy()){ drawSaveIndicator();} 182 | drawPaperBG(); 183 | } 184 | 185 | void drawSaveIndicator() { 186 | pushMatrix(); 187 | fill(color(200, 0, 0)); 188 | noStroke(); 189 | rect(0,0,width, 4); 190 | popMatrix(); 191 | } 192 | 193 | void drawUI() { 194 | printWField.draw(); 195 | printHField.draw(); 196 | noFill(); 197 | } 198 | 199 | void saveImage() { 200 | int _plotWPx = plotWPx; 201 | int _plotHPx = plotHPx; 202 | if(useRetinaDisplay){ 203 | _plotWPx *= 2; 204 | _plotHPx *= 2; 205 | } 206 | 207 | imgSaver.begin(plotWInches, plotHInches, _plotWPx , _plotHPx); 208 | } 209 | 210 | void focusField(Field f) { 211 | f.focus(); 212 | focusedField = f; 213 | fieldStringVal = ""; 214 | } 215 | 216 | void mousePressed() { 217 | if(printWField.mouseIsOver() || printHField.mouseIsOver()) { 218 | handleFieldClick(); 219 | } else { 220 | handleSketchClick(); 221 | } 222 | 223 | canvasMouseX = mouseX - canvasXPx; 224 | canvasMouseY = mouseY - canvasYPx; 225 | 226 | sketch.mousePressed(); 227 | } 228 | 229 | void handleSketchClick() { 230 | if(printWField.focused || printHField.focused){ 231 | printWInches = printWField.blur(); 232 | printHInches = printHField.blur(); 233 | updateKeyDimensions(); 234 | } 235 | } 236 | 237 | void handleFieldClick() { 238 | if(printWField.mouseIsOver()) { 239 | if(printWField.focused){ 240 | printWInches = printWField.blur(); 241 | } else { 242 | printHInches = printHField.blur(); 243 | focusField(printWField); 244 | } 245 | } else { 246 | if(printHField.focused){ 247 | printHInches = printHField.blur(); 248 | } else { 249 | printWInches = printWField.blur(); 250 | focusField(printHField); 251 | } 252 | } 253 | updateKeyDimensions(); 254 | } 255 | 256 | void handleFieldInput() { 257 | if((key >= 48 && key <= 57) || key == '.') { 258 | fieldStringVal += key; 259 | } 260 | 261 | if(keyCode == BACKSPACE){ 262 | fieldStringVal = fieldStringVal.substring(0, max(0, fieldStringVal.length() - 1)); 263 | } 264 | 265 | focusedField.setStringValue(fieldStringVal); 266 | 267 | if(keyCode == ENTER || keyCode == RETURN) { 268 | printWInches = printWField.blur(); 269 | printHInches = printHField.blur(); 270 | updateKeyDimensions(); 271 | } 272 | } 273 | 274 | 275 | void keyPressed() { 276 | if(printWField.focused || printHField.focused){ 277 | handleFieldInput(); 278 | } 279 | 280 | switch(key) { 281 | case 's' : 282 | saveImage(); 283 | break; 284 | case 'g': 285 | showGrid = !showGrid; 286 | showUI = !showUI; 287 | break; 288 | } 289 | 290 | sketch.keyPressed(); 291 | } 292 | 293 | void mouseReleased(){ 294 | sketch.mouseReleased(); 295 | } 296 | void keyReleased() { 297 | sketch.keyReleased(); 298 | } 299 | 300 | // ---------------------------------------- 301 | // GRAPH PAPER 302 | // ---------------------------------------- 303 | 304 | class GraphPaper { 305 | 306 | // max plotter dimensions in inches 307 | float maxW; 308 | float maxH; 309 | 310 | // offset to center grid on paper 311 | float x = 0; 312 | float y = 0; 313 | 314 | float ppi; 315 | 316 | GraphPaper(float _maxW, float _maxH, float _w, float _h, float _ppi) { 317 | maxW = _maxW; 318 | maxH = _maxH; 319 | 320 | if(_h > _w) setPortrait(); 321 | 322 | maxW = min(maxW, _w); 323 | maxH = min(maxH, _h); 324 | 325 | x = (_w - maxW) / 2 * _ppi; 326 | y = (_h - maxH) / 2 * _ppi; 327 | 328 | ppi = _ppi; 329 | } 330 | 331 | void setPortrait() { 332 | float tempH = maxH; 333 | maxH = maxW; 334 | maxW = tempH; 335 | } 336 | 337 | void draw() { 338 | stroke(150, 255, 255); 339 | strokeWeight(1); 340 | noFill(); 341 | 342 | int cols = round( maxW * 2) + 1; 343 | int colStart = 0; 344 | if(x <= 0) { 345 | colStart = 1; 346 | cols -= 1; 347 | } 348 | for(int i = colStart; i < cols; i++) { 349 | if(i % 2 == 0) { 350 | strokeWeight(1); 351 | } else { 352 | strokeWeight(0.5); 353 | } 354 | line(i * ppi /2 + x, y, i * ppi /2 + x, maxH * ppi + y); 355 | } 356 | 357 | int rows = round(maxH * 2) + 1; 358 | int rowStart = 0; 359 | if(y <= 0) { 360 | rowStart = 1; 361 | rows -= 1; 362 | } 363 | for(int i = rowStart; i < rows; i++) { 364 | if(i % 2 == 0) { 365 | strokeWeight(1); 366 | } else { 367 | strokeWeight(0.5); 368 | } 369 | line(x, i * ppi /2 + y, maxW * ppi + x, i * ppi /2 + y); 370 | } 371 | 372 | } 373 | } 374 | 375 | // ---------------------------------------- 376 | // IMAGE SAVER 377 | // ---------------------------------------- 378 | 379 | import java.util.regex.Pattern; 380 | import java.util.regex.Matcher; 381 | 382 | enum SaveState { 383 | NONE, 384 | BEGAN, 385 | SAVING, 386 | COMPLETE, 387 | RENDER_BEGAN, 388 | RENDERING 389 | } 390 | 391 | enum SaveMode { 392 | SVG, PNG 393 | } 394 | 395 | class ImageSaver { 396 | 397 | SaveState state = SaveState.NONE; 398 | String filename; 399 | boolean saveSVG = true; 400 | boolean savePNG = true; 401 | 402 | boolean didSaveSVG = false; 403 | boolean didSavePNG = false; 404 | 405 | SaveMode mode = SaveMode.SVG; 406 | 407 | float printW, printH; 408 | int canvasW, canvasH; 409 | 410 | ImageSaver() { 411 | 412 | } 413 | 414 | void updateSaveMode() { 415 | if(mode == SaveMode.SVG){ 416 | if(savePNG){ 417 | mode = SaveMode.PNG; 418 | } else { 419 | state = SaveState.COMPLETE; 420 | } 421 | } else { 422 | state = SaveState.COMPLETE; 423 | } 424 | } 425 | 426 | void update() { 427 | switch (state) { 428 | case BEGAN: 429 | println("\nSaving... "); 430 | state = SaveState.SAVING; 431 | break; 432 | case RENDER_BEGAN: 433 | state = SaveState.RENDERING; 434 | break; 435 | case SAVING: 436 | if(mode == SaveMode.SVG){ 437 | resizeSVG(filename, printW, printH, canvasW, canvasH); 438 | } 439 | updateSaveMode(); 440 | break; 441 | case RENDERING: 442 | // runRenderQueue(); 443 | break; 444 | case COMPLETE: 445 | println("DONE!"); 446 | state = SaveState.NONE; 447 | break; 448 | } 449 | } 450 | 451 | boolean isBusy() { 452 | return state == SaveState.BEGAN || 453 | state == SaveState.SAVING || 454 | state == SaveState.RENDER_BEGAN || 455 | state == SaveState.RENDERING; 456 | } 457 | 458 | void begin(float _printW, float _printH, int _canvasW, int _canvasH) { 459 | filename = getFileName(); 460 | state = SaveState.BEGAN; 461 | didSavePNG = false; 462 | didSaveSVG = false; 463 | 464 | printW = _printW; 465 | printH = _printH; 466 | 467 | canvasW = _canvasW; 468 | canvasH = _canvasH; 469 | 470 | if(saveSVG){ 471 | mode = SaveMode.SVG; 472 | } else if(savePNG){ 473 | mode = SaveMode.PNG; 474 | } 475 | } 476 | 477 | void startSave() { 478 | if(state == SaveState.SAVING) { 479 | if(mode == SaveMode.SVG){ 480 | print("- saving SVG... "); 481 | beginRecord(SVG, "output/" + filename + ".svg"); 482 | } 483 | } 484 | } 485 | 486 | void endSave(PImage screenImage, int x, int y, int w, int h) { 487 | 488 | if(state == SaveState.SAVING) { 489 | if(mode == SaveMode.SVG){ 490 | endRecord(); 491 | println("done!"); 492 | } else { 493 | savePreview(screenImage, x, y, w, h); 494 | } 495 | } 496 | update(); 497 | } 498 | 499 | void savePreview(PImage screenImage, int x, int y, int w, int h) { 500 | print("- saving PNG preview... "); 501 | PGraphics preview = createGraphics(w, h); 502 | preview.beginDraw(); 503 | preview.background(255); 504 | preview.image(screenImage, -x, -y); 505 | preview.endDraw(); 506 | preview.save("output/" + filename + ".png"); 507 | println("done!"); 508 | } 509 | 510 | void resizeSVG(String filename, float wInches, float hInches, int wPx, int hPx){ 511 | print("- resizing SVG... "); 512 | String[] lines = loadStrings("output/" + filename + ".svg"); 513 | 514 | for(int i=0; i < lines.length; i++){ 515 | String l = lines[i]; 516 | if(l.length() > 4 && l.substring(0, 4).equals(" 0){ 613 | stringValue = stringValue.replaceAll("0*$", ""); 614 | stringValue = stringValue.replaceAll("\\.$", ""); 615 | } 616 | } 617 | 618 | boolean mouseIsOver() { 619 | return mouseX > x && mouseX < x + 50 && mouseY > y && mouseY < y + 20; 620 | } 621 | } 622 | 623 | // ---------------------------------------- 624 | // PLOTTER CANVAS 625 | // ---------------------------------------- 626 | 627 | // Sketch extends this 628 | // I only did this to hide the boilerplate from the Sketch file 629 | class PlotterCanvas { 630 | int w; // width in pixels 631 | int h; // height in pixels 632 | float ppi; // pixels per inch of printed doc 633 | float strokeWeight; // the scaled stroke weight for the sketch 634 | float screenScale; // the scale factor for the canvas 635 | 636 | PlotterCanvas() {} 637 | 638 | // this gets called whenever the canvas size changes 639 | // you can override it in Sketch, but make sure to call super 640 | void setDimensions(int _w, int _h, float _screenScale, float _ppi, float _strokeWeight) { 641 | w = _w; 642 | h = _h; 643 | screenScale = _screenScale; 644 | ppi = _ppi; 645 | strokeWeight = _strokeWeight; 646 | } 647 | 648 | // these all get overridden in the Sketch 649 | void draw() {} 650 | void mousePressed() {} 651 | void mouseReleased() {} 652 | void keyPressed() {} 653 | void keyReleased() {} 654 | } -------------------------------------------------------------------------------- /app/config.pde: -------------------------------------------------------------------------------- 1 | // some helpful constants 2 | 3 | // SCREEN SCALES 4 | // helps scale canvas to appear 1:1 on screen 5 | float MACBOOK_15_SCALE = 0.4912; 6 | float MACBOOK_13_SCALE = 0.5; 7 | float LG_DISPLAY_SCALE = 0.366; 8 | 9 | // PEN SIZES (MM) 10 | float RAPIDOGRAPH_0 = 0.35; 11 | float RAPIDOGRAPH_7 = 2.0; 12 | float BIC_INTENSITY = 0.5; 13 | float POSCA_3M = 1.2; 14 | float POSCA_5M = 2.4; 15 | float PILOT_V5 = 0.5; 16 | float PILOT_V7 = 0.7; 17 | 18 | // ---------------------------- 19 | // ### PROJECT CONFIG ### 20 | 21 | // SCREEN SETTINGS 22 | boolean useRetinaDisplay = true; 23 | boolean fullscreen = true; 24 | float displayScale = MACBOOK_13_SCALE; 25 | 26 | // PRINT SETTINGS (in inches) 27 | float printW = 17; 28 | float printH = 14; 29 | 30 | // PLOTTER SETTINGS (in inches) 31 | float maxPlotW = 17; 32 | float maxPlotH = 11; 33 | boolean constrainToPlotArea = false; 34 | 35 | // pen thickness (in mm) 36 | float penSize = POSCA_3M; 37 | 38 | // save a PNG preview alongside SVG 39 | boolean savePNGPreview = true; --------------------------------------------------------------------------------