No Description

main.go 13KB

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