Browse Source

seems to be working nice

Keelan Lightfoot 1 year ago
parent
commit
6e4997d4de
1 changed files with 325 additions and 223 deletions
  1. 325
    223
      main.go

+ 325
- 223
main.go View File

@@ -3,6 +3,7 @@ package main
3 3
 import (
4 4
 	"bufio"
5 5
 	"fmt"
6
+	"io"
6 7
 	"log"
7 8
 	"math"
8 9
 	"os"
@@ -11,45 +12,50 @@ import (
11 12
 	"strings"
12 13
 )
13 14
 
14
-type command struct {
15
-	verb string
16
-	noun float64
15
+type Word struct {
16
+	Address string
17
+	Number  float64
17 18
 }
18 19
 
19
-type line struct {
20
-	commands []command
20
+type GCodeFile struct {
21
+	Blocks map[int]*Block
21 22
 }
22 23
 
23
-func (l *line) Empty() bool {
24
-	return len(l.commands) == 0
24
+type Block struct {
25
+	Words []Word
25 26
 }
26 27
 
27
-func (l *line) String() string {
28
+const (
29
+	ScaleInch float64 = 25.4
30
+	ScaleMM   float64 = 1
31
+)
32
+
33
+func (b *Block) String() string {
28 34
 	s := []string{}
29
-	for _, cmd := range l.commands {
30
-		_, frac := math.Modf(cmd.noun)
35
+	for _, cmd := range b.Words {
36
+		_, frac := math.Modf(cmd.Number)
31 37
 		if frac == 0 {
32
-			s = append(s, fmt.Sprintf("%s%0.0f", cmd.verb, cmd.noun))
38
+			s = append(s, fmt.Sprintf("%s%0.0f", cmd.Address, cmd.Number))
33 39
 		} else {
34
-			s = append(s, fmt.Sprintf("%s%f", cmd.verb, cmd.noun))
40
+			s = append(s, fmt.Sprintf("%s%f", cmd.Address, cmd.Number))
35 41
 		}
36 42
 	}
37 43
 	return strings.Join(s, " ")
38 44
 }
39 45
 
40
-func (l *line) get(verb string) (float64, bool) {
41
-	for _, c := range l.commands {
42
-		if c.verb == verb {
43
-			return c.noun, true
46
+func (l *Block) Get(Address string) (float64, bool) {
47
+	for _, c := range l.Words {
48
+		if c.Address == Address {
49
+			return c.Number, true
44 50
 		}
45 51
 	}
46 52
 	return 0, false
47 53
 }
48 54
 
49
-func (l *line) has(verb string, noun float64) bool {
55
+func (l *Block) Has(Address string, Number float64) bool {
50 56
 	fmt.Println(l)
51
-	for _, c := range l.commands {
52
-		if c.verb == verb && c.noun == noun {
57
+	for _, c := range l.Words {
58
+		if c.Address == Address && c.Number == Number {
53 59
 			return true
54 60
 		}
55 61
 	}
@@ -59,63 +65,47 @@ func (l *line) has(verb string, noun float64) bool {
59 65
 type point struct {
60 66
 	x, y       float64
61 67
 	lineNumber int
68
+	scale      float64
69
+}
70
+
71
+func (p point) scaleTo(s float64) point {
72
+	return point{
73
+		x:          p.x * p.scale / s,
74
+		y:          p.y * p.scale / s,
75
+		lineNumber: p.lineNumber,
76
+		scale:      s,
77
+	}
62 78
 }
63 79
 
64
-func (p *point) IsZero() bool {
65
-	return p.x == 0 && p.y == 0
80
+func (p point) millimeters() point {
81
+	return p.scaleTo(ScaleMM)
66 82
 }
67 83
 
68
-var scale = 1.0
84
+func (p point) inches() point {
85
+	return p.scaleTo(ScaleInch)
86
+}
69 87
 
70 88
 func main() {
71
-	file, err := os.Open("tp3.nc")
72 89
 
90
+	gc, err := ReadGCodeFile("testwrench.nc")
73 91
 	if err != nil {
74 92
 		log.Fatal(err)
75 93
 	}
76
-	defer file.Close()
77
-
78
-	r := regexp.MustCompile(`([A-Z])(\-?[0-9]+\.?[0-9]*)`)
79
-
80
-	lines := make(map[int]*line)
81
-	scanner := bufio.NewScanner(file)
82
-	lineNumber := 0
83
-	for scanner.Scan() {
84
-		lineStr := scanner.Text()
85
-		l := &line{}
86
-		matches := r.FindAllStringSubmatch(lineStr, -1)
87
-		for _, cmd := range matches {
88
-			verb := cmd[1]
89
-			noun, err := strconv.ParseFloat(cmd[2], 64)
90
-			if err != nil {
91
-				log.Fatal(err)
92
-			}
93
-			l.commands = append(l.commands, command{verb: verb, noun: noun})
94
-		}
95
-		lines[lineNumber] = l
96
-		lineNumber++
97
-
98
-	}
99
-
100
-	log.Printf("Read %d lines.", len(lines))
101
-	if err := scanner.Err(); err != nil {
102
-		log.Fatal(err)
103
-	}
104 94
 
105 95
 	mode := ""
106 96
 	var x, y float64
107 97
 	var lastPoints []point
108 98
 	arcs := []*arc{}
99
+	scale := ScaleMM
109 100
 
110
-	for lineNumber := 0; lineNumber < len(lines); lineNumber++ {
111
-		line := lines[lineNumber]
101
+	for lineNumber := 0; lineNumber < len(gc.Blocks); lineNumber++ {
102
+		block := gc.Blocks[lineNumber]
112 103
 		xyChanged, zChanged, modeChanged := false, false, false
113
-		//oldPt := point{x:x,y:y}
114
-		for _, cmd := range line.commands {
104
+		for _, cmd := range block.Words {
115 105
 			oldMode := mode
116
-			switch cmd.verb {
106
+			switch cmd.Address {
117 107
 			case "G":
118
-				switch cmd.noun {
108
+				switch cmd.Number {
119 109
 				case 0:
120 110
 					mode = "g0"
121 111
 				case 1:
@@ -125,19 +115,18 @@ func main() {
125 115
 				case 3:
126 116
 					mode = "g3"
127 117
 				case 20:
128
-					//fmt.Println("inches!")
129
-					scale = 25.4
118
+					scale = ScaleInch
130 119
 				case 21:
131
-					scale = 1
120
+					scale = ScaleMM
132 121
 				}
133 122
 			case "X":
134
-				x = cmd.noun
123
+				x = cmd.Number
135 124
 				xyChanged = true
136 125
 			case "Y":
137
-				y = cmd.noun
126
+				y = cmd.Number
138 127
 				xyChanged = true
139 128
 			case "Z":
140
-				//z = cmd.noun
129
+				//z = cmd.Number
141 130
 				zChanged = true
142 131
 			}
143 132
 			if mode != oldMode {
@@ -145,11 +134,11 @@ func main() {
145 134
 			}
146 135
 		}
147 136
 
148
-		newPt := point{x: x, y: y, lineNumber: lineNumber}
137
+		newPt := point{x: x, y: y, lineNumber: lineNumber, scale: scale}
149 138
 
150 139
 		if zChanged || modeChanged {
151 140
 			if len(lastPoints) > 0 {
152
-				arcs = append(arcs, checkPoints4(lastPoints)...)
141
+				arcs = append(arcs, findArcs(lastPoints, scale)...)
153 142
 				lastPoints = make([]point, 0)
154 143
 			}
155 144
 		} else if mode == "g1" && xyChanged {
@@ -159,211 +148,331 @@ func main() {
159 148
 	}
160 149
 
161 150
 	if len(lastPoints) > 0 {
162
-		arcs = append(arcs, checkPoints4(lastPoints)...)
151
+		arcs = append(arcs, findArcs(lastPoints, scale)...)
163 152
 	}
164 153
 
165
-	// for _, arc := range arcs {
166
-	// 		fmt.Printf("-----------------------------\n")
167
-	// 		fmt.Printf("     Lines: %d-%d\n",arc.startLine, arc.endLine)
168
-	// 		fmt.Printf("    Centre: %0.2f, %0.2f\n", arc.centre.x,arc.centre.y )
169
-	// 		fmt.Printf("        SD: %0.4f\n", arc.bestSD )
170
-	// 	}
171
-	//
172
-
173
-	numLines := len(lines)
174
-
175 154
 	for _, arc := range arcs {
176
-		i := arc.centre.x - arc.startPt.x
177
-		j := arc.centre.y - arc.startPt.y
155
+
156
+		i := (arc.centre.x - arc.startPt.x)
157
+		j := (arc.centre.y - arc.startPt.y)
178 158
 
179 159
 		// clear out old commands
180
-		for i := arc.startLine; i <= arc.endLine; i++ {
181
-			delete(lines, i)
160
+		for i := arc.startBlock; i <= arc.endBlock; i++ {
161
+			delete(gc.Blocks, i)
182 162
 		}
183 163
 
184
-		//
185
-		lines[arc.startLine] = &line{commands: []command{
186
-			{verb: "G", noun: 1},
187
-			{verb: "X", noun: arc.startPt.x},
188
-			{verb: "Y", noun: arc.startPt.y},
164
+		// G1 to move to the beginning point of the arc
165
+		gc.Blocks[arc.startBlock] = &Block{Words: []Word{
166
+			{Address: "G", Number: 1},
167
+			{Address: "X", Number: arc.startPt.x},
168
+			{Address: "Y", Number: arc.startPt.y},
189 169
 		}}
170
+
171
+		// G2/G3 to do the arc
190 172
 		dir := 3.0
191 173
 		if arc.clockwise {
192 174
 			dir = 2.0
193 175
 		}
194
-		lines[arc.startLine+1] = &line{commands: []command{
195
-			{verb: "G", noun: dir},
196
-			{verb: "I", noun: i},
197
-			{verb: "J", noun: j},
198
-			{verb: "X", noun: arc.endPt.x},
199
-			{verb: "Y", noun: arc.endPt.y},
176
+		gc.Blocks[arc.startBlock+1] = &Block{Words: []Word{
177
+			{Address: "G", Number: dir},
178
+			{Address: "I", Number: i},
179
+			{Address: "J", Number: j},
180
+			{Address: "X", Number: arc.endPt.x},
181
+			{Address: "Y", Number: arc.endPt.y},
200 182
 		}}
201
-		lines[arc.startLine+2] = &line{commands: []command{
202
-			{verb: "G", noun: 1},
183
+
184
+		// shove in a G1 for the surviving X and Y commands after the arc
185
+		gc.Blocks[arc.startBlock+2] = &Block{Words: []Word{
186
+			{Address: "G", Number: 1},
203 187
 		}}
204 188
 	}
205 189
 
206
-	for i := 0; i < numLines; i++ {
207
-		if lines[i] != nil {
208
-			fmt.Println(lines[i].String())
190
+	gc.Write(os.Stdout)
191
+
192
+}
193
+
194
+func ReadGCodeFile(filename string) (*GCodeFile, error) {
195
+	file, err := os.Open(filename)
196
+	if err != nil {
197
+		return nil, err
198
+	}
199
+	defer file.Close()
200
+
201
+	r := regexp.MustCompile(`([A-Z])(\-?[0-9]+\.?[0-9]*)`)
202
+
203
+	lines := make(map[int]*Block)
204
+	scanner := bufio.NewScanner(file)
205
+	lineNumber := 0
206
+	for scanner.Scan() {
207
+		lineStr := scanner.Text()
208
+		l := &Block{}
209
+		matches := r.FindAllStringSubmatch(lineStr, -1)
210
+		for _, cmd := range matches {
211
+			address := cmd[1]
212
+			number, err := strconv.ParseFloat(cmd[2], 64)
213
+			if err != nil {
214
+				return nil, err
215
+			}
216
+			l.Words = append(l.Words, Word{Address: address, Number: number})
209 217
 		}
218
+		lines[lineNumber] = l
219
+		lineNumber++
220
+
210 221
 	}
222
+
223
+	log.Printf("Read %d lines.", len(lines))
224
+	if err := scanner.Err(); err != nil {
225
+		return nil, err
226
+	}
227
+
228
+	return &GCodeFile{Blocks: lines}, nil
211 229
 }
212 230
 
213
-// func  (p *cr)dist(n cr) float64 {
214
-// 	return math.Sqrt(math.Pow((p.r-n.r), 2))
215
-// }
231
+func (g *GCodeFile) Write(w io.Writer) error {
232
+	maxBlock := 0
233
+	for n := range g.Blocks {
234
+		if n > maxBlock {
235
+			maxBlock = n
236
+		}
237
+	}
238
+	for i := 0; i <= maxBlock; i++ {
239
+		if g.Blocks[i] != nil {
240
+			_, err := fmt.Fprintf(w, "%s\n", g.Blocks[i].String())
241
+			if err != nil {
242
+				return err
243
+			}
244
+		}
245
+	}
246
+	return nil
247
+}
216 248
 
217 249
 type arc struct {
218
-	startLine  int
219
-	endLine    int
250
+	startBlock int
251
+	endBlock   int
220 252
 	startPt    point
221
-	secondPt   point
222 253
 	endPt      point
223 254
 	radius     float64
224 255
 	centre     point
225 256
 	clockwise  bool
226
-	startIndex int
227
-	endIndex   int
228 257
 	bestSD     float64
258
+	scale      float64
229 259
 }
230 260
 
231
-type curveInfo struct {
261
+func (a *arc) scaleTo(s float64) *arc {
262
+	n := *a
263
+	n.startPt = a.startPt.scaleTo(s)
264
+	n.endPt = a.endPt.scaleTo(s)
265
+	n.centre = a.centre.scaleTo(s)
266
+	n.radius = a.radius * a.scale / s
267
+	n.scale = s
268
+	return &n
269
+}
270
+
271
+type arcMidpoint struct {
232 272
 	midpoint int
233 273
 	width    int
234 274
 }
235 275
 
236
-func checkPoints4(points []point) []*arc {
237
-
238
-	numTests := 3
239
-
240
-	state := "searching"
241
-
242
-	curveMidPoints := []curveInfo{}
243
-
244
-	curveStart := 0
276
+// findArcs identifies any arcs contained in the given points, and returns
277
+// them as arc objects. Internally it operates in millimeters, but scales
278
+// all inputs and outputs according to the given scale value
279
+//
280
+// The algorithm works by first locating the mid-points of all arcs. It's easy
281
+// to find things that aren't arcs, so the algorithm assumes that the space
282
+// between two not-arcs is an arc. The midpoint of the not-arc is determined,
283
+// then the arc is grown from the midpoint until it becomes too un-arc-like to
284
+// be considered an arc.
285
+
286
+func findArcs(points []point, scale float64) []*arc {
287
+
288
+	numTests := 4      // how many circles to test
289
+	thresh := 5.0      // 5.0 is definitely not an arc. This value was determined empirically because I'm a slob
290
+	growThresh := 0.01 // while in an arc, anything that causes the sd to increase above growThresh terminates the arc
291
+	growThresh = 0.05
292
+	arcs := []*arc{}
245 293
 
246
-	for i := 0; i < len(points)-3*numTests; i++ {
294
+	// first find things that aren't an arc
295
+	inArc := false
296
+	arcStart := 0
297
+	numTestPoints := len(points) - 3*numTests
298
+	for i := 0; i < numTestPoints; i++ {
247 299
 
248 300
 		vX := make([]float64, numTests)
249 301
 		vY := make([]float64, numTests)
250 302
 		vR := make([]float64, numTests)
251
-		vD := make([]float64, numTests)
252 303
 		for j := 0; j < numTests; j++ {
253
-			vX[j], vY[j], vR[j] = circleFromThreePointsXYR(points[i+j], points[i+j+numTests], points[i+j+(2*numTests)])
254
-			vX[j] *= scale
255
-			vY[j] *= scale
256
-			vR[j] *= scale
257
-			vD[j] = (vX[j]-points[i+j].x)*(points[i+j+1].y-points[i+j].y) - (vY[j]-points[i+j].y)*(points[i+j+1].x-points[i+j].x)
304
+			vX[j], vY[j], vR[j] = circleFromThreePointsXYR(points[i+j].millimeters(), points[i+j+numTests].millimeters(), points[i+j+(2*numTests)].millimeters())
305
+		}
306
+		maxDev := max([]float64{stdDev(vX), stdDev(vY), stdDev(vR)})
307
+
308
+		if inArc && (maxDev >= thresh || i == numTestPoints-1) {
309
+			fmt.Println("=====")
310
+			inArc = false
311
+			mid := arcStart + ((i - arcStart) / 2)
312
+
313
+			midpoint := mid + (3*numTests)/2         // hand tuned compensation for algorithm lag
314
+			width := (i - arcStart) + (5 * numTests) // hand tuned compensation for algorithm sensitivity
315
+
316
+			bestStart, bestEnd, bestCenter, bestRadius, bestSD := growArc(points, midpoint, width, growThresh)
317
+			fmt.Printf("TEST %d 0 %d %d %0.8f\n", points[midpoint].lineNumber, bestStart, bestEnd, bestSD)
318
+
319
+			// for q:=-8;q<=8;q++ {
320
+			// 				for v:=-8;v<=8;v++ {
321
+			// 					bestStart2, bestEnd2, bestCenter2, bestRadius2, bestSD2 := testArc(points, midpoint+v, width+q, bestSD)
322
+			// 					fmt.Printf("TEST mid=%d tweak=%d tweak2=%d start=%d end=%d sd=%0.8f oldbest=%0.8f\n", points[midpoint+v].lineNumber, v, q, bestStart2, bestEnd2, bestSD2, bestSD)
323
+			// 					if bestSD2 < bestSD {
324
+			// 								fmt.Println("POOOP")
325
+			//
326
+			// 						bestStart, bestEnd, bestCenter, bestRadius, bestSD = bestStart2, bestEnd2, bestCenter2, bestRadius2, bestSD2
327
+			// 					}
328
+			// 				}
329
+			// 			}
330
+
331
+			startPt := points[bestStart].millimeters()
332
+			endPt := points[bestEnd].millimeters()
333
+			secondPt := points[bestStart+2].millimeters()
334
+			clockwise := ((bestCenter.x-startPt.x)*(secondPt.y-startPt.y) - (bestCenter.y-startPt.y)*(secondPt.x-startPt.x)) > 0
335
+
336
+			arc := (&arc{
337
+				startPt:    startPt,
338
+				endPt:      endPt,
339
+				startBlock: startPt.lineNumber,
340
+				endBlock:   endPt.lineNumber,
341
+				centre:     bestCenter,
342
+				radius:     bestRadius,
343
+				bestSD:     bestSD,
344
+				clockwise:  clockwise,
345
+				scale:      ScaleMM,
346
+			}).scaleTo(scale)
347
+			arcs = append(arcs, arc)
348
+
349
+		} else if !inArc && maxDev < thresh {
350
+			inArc = true
351
+			arcStart = i
258 352
 		}
259
-		sd1 := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2))
260
-		//	sd2 := math.Sqrt(math.Pow(stdDev(vR), 2))
261 353
 
262
-		//fmt.Printf("%d\t%f\n",points[i+9].lineNumber,sd1)
354
+	}
263 355
 
264
-		if i == len(points)-3*numTests-1 && state == "curve" {
265
-			sd1 = 100 // kludge
266
-		}
267
-		thresh := 2.5 // 2.5
268
-		switch state {
269
-		case "searching":
270
-			if sd1 >= thresh {
271
-				state = "deadzone"
272
-			} else {
273
-				state = "curve"
274
-				curveStart = i
275
-			}
276
-		case "deadzone":
277
-			if sd1 < thresh {
278
-				state = "curve"
279
-				curveStart = i
280
-			}
281
-		case "curve":
282
-			if sd1 >= thresh {
283
-				mid := curveStart + ((i - curveStart) / 2)
284
-				//fmt.Printf("curve start = %d end = %d mid = %d\n", points[curveStart+9].lineNumber, points[i+9].lineNumber, points[mid+9].lineNumber)
285
-				curveMidPoints = append(curveMidPoints, curveInfo{
286
-					midpoint: mid + (3*numTests)/2 - 1,
287
-					width:    (i - curveStart) + (4 * numTests),
288
-				})
289
-				state = "deadzone"
290
-			}
356
+	return arcs
357
+}
291 358
 
359
+func growArc(points []point, midpoint int, width int, thresh float64) (int, int, point, float64, float64) {
360
+	vX := make([]float64, 0)
361
+	vY := make([]float64, 0)
362
+	vR := make([]float64, 0)
363
+	var bestStart, bestEnd int
364
+	var bestCenter point
365
+	var bestRadius float64
366
+
367
+	bestSD := 100.0
368
+
369
+	for i := width / 3; i < width/2; i++ {
370
+		lowerPt := midpoint - i
371
+		if lowerPt < 0 {
372
+			lowerPt = 0
373
+		}
374
+		upperPt := midpoint + i
375
+		if upperPt > len(points)-1 {
376
+			upperPt = len(points) - 1
377
+		}
378
+		x, y, r := circleFromThreePointsXYR(points[lowerPt].millimeters(), points[midpoint].millimeters(), points[upperPt].millimeters())
379
+		vX = append(vX, x)
380
+		vY = append(vY, y)
381
+		vR = append(vR, r)
382
+		//	fmt.Printf("lines: %d, %d %d = %0.2f,%0.2f,%0.2f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, x*scale, y*scale, r*scale)
383
+		//	sd := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
384
+
385
+		sd := max([]float64{stdDev(vX), stdDev(vY), stdDev(vR)})
386
+
387
+		//fmt.Printf("Averages: %d, %d %d = %0.4f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, sd)
388
+		if sd > thresh {
389
+			break
292 390
 		}
391
+		bestStart, bestEnd = lowerPt, upperPt
392
+		bestCenter = point{x: mean(vX), y: mean(vY), scale: ScaleMM}
393
+		bestRadius = mean(vR)
394
+		bestSD = sd
395
+
396
+		//	fmt.Printf("Line %d SD=%0.4f start=%d end=%d\n",points[midpoint.midpoint].lineNumber, sd1, points[lowerPt].lineNumber, points[upperPt].lineNumber)
397
+
293 398
 	}
294
-	//fmt.Println(curveMidPoints)
295
-	spillover := 0
296 399
 
297
-	arcs := []*arc{}
400
+	return bestStart, bestEnd, bestCenter, bestRadius, bestSD
401
+}
298 402
 
299
-	for _, midpoint := range curveMidPoints {
300
-		vX := make([]float64, 0)
301
-		vY := make([]float64, 0)
302
-		vR := make([]float64, 0)
303
-
304
-		var bestStart, bestEnd int
305
-		var bestCenter point
306
-		var bestRadius float64
307
-		thresh := .01
308
-		bestSD := 100.0
309
-
310
-		//	fmt.Println("----------")
311
-		for i := midpoint.width / 3; i < midpoint.width/2+spillover; i++ {
312
-			lowerPt := midpoint.midpoint - i
313
-			if lowerPt < 0 {
314
-				lowerPt = 0
315
-			}
316
-			upperPt := midpoint.midpoint + i
317
-			if upperPt > len(points)-1 {
318
-				upperPt = len(points) - 1
319
-			}
320
-			x, y, r := circleFromThreePointsXYR(points[lowerPt], points[midpoint.midpoint], points[upperPt])
321
-			vX = append(vX, x*scale)
322
-			vY = append(vY, y*scale)
323
-			vR = append(vR, r*scale)
324
-			//	fmt.Printf("lines: %d, %d %d = %0.2f,%0.2f,%0.2f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, x*scale, y*scale, r*scale)
325
-			sd1 := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
326
-
327
-			//fmt.Printf("Averages: %d, %d %d = %0.4f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, sd1)
328
-			if sd1 > thresh {
329
-				break
330
-			}
403
+func testArc(points []point, midpoint int, width int, bestSD float64) (int, int, point, float64, float64) {
404
+	vX := make([]float64, 0)
405
+	vY := make([]float64, 0)
406
+	vR := make([]float64, 0)
407
+	var bestStart, bestEnd int
408
+	var bestCenter point
409
+	var bestRadius float64
410
+
411
+	fmt.Printf("Testing %d points\n", width/2-width/3)
412
+	n := 1
413
+	for i := width / 3; i < width/2; i++ {
414
+		lowerPt := midpoint - i
415
+		if lowerPt < 0 {
416
+			lowerPt = 0
417
+		}
418
+		upperPt := midpoint + i
419
+		if upperPt > len(points)-1 {
420
+			upperPt = len(points) - 1
421
+		}
422
+		x, y, r := circleFromThreePointsXYR(points[lowerPt].millimeters(), points[midpoint].millimeters(), points[upperPt].millimeters())
423
+		vX = append(vX, x)
424
+		vY = append(vY, y)
425
+		vR = append(vR, r)
426
+		fmt.Printf("blocks: %d, %d %d\n", points[lowerPt].lineNumber, points[midpoint].lineNumber, points[upperPt].lineNumber)
427
+		//	sd := math.Sqrt(math.Pow(stdDev(vX), 2) + math.Pow(stdDev(vY), 2) + math.Pow(stdDev(vR), 2))
428
+		sd := max([]float64{stdDev(vX), stdDev(vY), stdDev(vR)})
429
+		fmt.Printf("sds [n=%d]: %0.4f  %0.4f  %0.4f (test=%0.8f, best=%0.8f)\n", n, stdDev(vX), stdDev(vY), stdDev(vR), sd, bestSD)
430
+
431
+		//fmt.Printf("Averages: %d, %d %d = %0.4f\n", points[lowerPt].lineNumber, points[midpoint.midpoint].lineNumber, points[upperPt].lineNumber, sd)
432
+		if sd < bestSD && n > 3 {
331 433
 			bestStart, bestEnd = lowerPt, upperPt
332
-			bestCenter = point{x: avg(vX), y: avg(vY)}
333
-			bestRadius = avg(vR)
334
-			bestSD = sd1
434
+			bestCenter = point{x: mean(vX), y: mean(vY), scale: ScaleMM}
435
+			bestRadius = mean(vR)
436
+			bestSD = sd
437
+		}
438
+		n++
439
+		//	fmt.Printf("Line %d SD=%0.4f start=%d end=%d\n",points[midpoint.midpoint].lineNumber, sd1, points[lowerPt].lineNumber, points[upperPt].lineNumber)
335 440
 
336
-			//	fmt.Printf("Line %d SD=%0.4f start=%d end=%d\n",points[midpoint.midpoint].lineNumber, sd1, points[lowerPt].lineNumber, points[upperPt].lineNumber)
441
+	}
337 442
 
338
-		}
443
+	return bestStart, bestEnd, bestCenter, bestRadius, bestSD
444
+}
339 445
 
340
-		arc := arc{
341
-			startPt:    points[bestStart],
342
-			secondPt:   points[bestStart+2],
343
-			endPt:      points[bestEnd],
344
-			startLine:  points[bestStart].lineNumber,
345
-			endLine:    points[bestEnd].lineNumber,
346
-			startIndex: bestStart,
347
-			endIndex:   bestEnd,
348
-			centre:     point{x: bestCenter.x / scale, y: bestCenter.y / scale},
349
-			radius:     bestRadius / scale,
350
-			bestSD:     bestSD,
446
+func max(num []float64) float64 {
447
+	m := 0.0
448
+	for _, n := range num {
449
+		if n > m {
450
+			m = n
351 451
 		}
352
-		d := (arc.centre.x-arc.startPt.x)*(arc.secondPt.y-arc.startPt.y) - (arc.centre.y-arc.startPt.y)*(arc.secondPt.x-arc.startPt.x)
353
-		arc.clockwise = d > 0
354
-		//	fmt.Println("QQQ", bestStart, bestEnd, d, arc.clockwise )
452
+	}
453
+	return m
454
+}
355 455
 
356
-		arcs = append(arcs, &arc)
456
+func min(num []float64) float64 {
457
+	m := num[0]
458
+	for _, n := range num {
459
+		if n < m {
460
+			m = n
461
+		}
357 462
 	}
358
-	return arcs
463
+	return m
359 464
 }
360 465
 
361
-func stdDev(num []float64) float64 {
466
+func mean(num []float64) float64 {
362 467
 	sum := 0.0
363 468
 	for _, n := range num {
364 469
 		sum += n
365 470
 	}
366
-	mean := sum / float64(len(num))
471
+	return sum / float64(len(num))
472
+}
473
+
474
+func stdDev(num []float64) float64 {
475
+	mean := mean(num)
367 476
 	sd := 0.0
368 477
 	for _, n := range num {
369 478
 		sd += math.Pow(n-mean, 2)
@@ -372,20 +481,13 @@ func stdDev(num []float64) float64 {
372 481
 	return sd
373 482
 }
374 483
 
375
-func avg(num []float64) float64 {
376
-	sum := 0.0
377
-	for _, n := range num {
378
-		sum += n
379
-	}
380
-	return sum / float64(len(num))
381
-}
382
-
383
-func circleFromThreePointsXYR(p1 point, p2 point, p3 point) (float64, float64, float64) {
384
-	A := p1.x*(p2.y-p3.y) - p1.y*(p2.x-p3.x) + p2.x*p3.y - p3.x*p2.y
385
-	B := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p3.y-p2.y) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p1.y-p3.y) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p2.y-p1.y)
386
-	C := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p2.x-p3.x) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p3.x-p1.x) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p1.x-p2.x)
387
-	x := -1 * B / (2.0 * A)
388
-	y := -1 * C / (2.0 * A)
484
+func circleFromThreePointsXYR(p1, p2, p3 point) (float64, float64, float64) {
485
+	a := p1.x*(p2.y-p3.y) - p1.y*(p2.x-p3.x) + p2.x*p3.y - p3.x*p2.y
486
+	b := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p3.y-p2.y) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p1.y-p3.y) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p2.y-p1.y)
487
+	c := (math.Pow(p1.x, 2)+math.Pow(p1.y, 2))*(p2.x-p3.x) + (math.Pow(p2.x, 2)+math.Pow(p2.y, 2))*(p3.x-p1.x) + (math.Pow(p3.x, 2)+math.Pow(p3.y, 2))*(p1.x-p2.x)
488
+	x := -1 * b / (2.0 * a)
489
+	y := -1 * c / (2.0 * a)
389 490
 	r := math.Sqrt(math.Pow((x-p1.x), 2) + math.Pow((y-p1.y), 2))
491
+	//fmt.Println(r)
390 492
 	return x, y, r
391 493
 }