├── .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 |
63 |
--------------------------------------------------------------------------------
/data/midsquare.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/data/midsquares.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------