2 Ревизии

Автор SHA1 Съобщение Дата
  Keelan Lightfoot 303a0e3850 reworked to do arc at a time преди 1 година
  Keelan Lightfoot 6e4997d4de seems to be working nice преди 1 година
променени са 1 файла, в които са добавени 383 реда и са изтрити 261 реда
  1. 383
    261
      main.go

+ 383
- 261
main.go Целия файл

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