Advertisement
Guest User

Compensate.lua

a guest
Aug 14th, 2024
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.00 KB | None | 0 0
  1. -- Global variables
  2. dbg = false
  3. previouslyWentLeft = false
  4. backlash = 0.05
  5.  
  6. -- Example usage
  7. local inputFile = "input.tap"
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19. function msg(data)
  20. if not dbg then return end
  21. local text = "(" .. debug.getinfo(2, "n").name .. ": " .. data[1] .. ")"
  22. for id = 2, #data do
  23. text = string.gsub(text, "%%", tostring(data[id]), 1)
  24. end
  25. print(text)
  26. end
  27.  
  28. -- Helper function to format G-code commands
  29. function formatGCode(command, params)
  30. local parts = {command}
  31. local keys = {"X", "Y", "Z", "R", "I", "J", "K", "F"}
  32. for _, key in ipairs(keys) do
  33. if params[key] then
  34. table.insert(parts, string.format("%s%.3f", key, params[key]))
  35. end
  36. end
  37. return table.concat(parts, " ")
  38. end
  39.  
  40. -- Function to find the center of the arc
  41. function findArcCenter(params, nominalXPos, nominalYPos, clockwise)
  42. local cx, cy
  43.  
  44. -- Calculate center based on I and J parameters
  45. if params.I and params.J then
  46. cx = nominalXPos + params.I
  47. cy = nominalYPos + params.J
  48. elseif params.I then
  49. cx = nominalXPos + params.I
  50. cy = nominalYPos -- Assume J is the same as the current Y position
  51. elseif params.J then
  52. cx = nominalXPos
  53. cy = nominalYPos + params.J -- Assume I is the same as the current X position
  54. elseif params.R then
  55. -- Calculate center based on R parameter
  56. local radius = params.R
  57. local deltaX, deltaY = params.X - nominalXPos, params.Y - nominalYPos
  58. local distance = math.sqrt(deltaX * deltaX + deltaY * deltaY)
  59.  
  60. if distance > 2 * math.abs(radius) then
  61. error("The distance between the points is greater than the diameter of the circle defined by R")
  62. end
  63.  
  64. local halfDistance = distance / 2
  65. local halfChordLength = math.sqrt(radius * radius - halfDistance * halfDistance)
  66. local midX, midY = (nominalXPos + params.X) / 2, (nominalYPos + params.Y) / 2
  67.  
  68. local offsetX = halfChordLength * (deltaY / distance)
  69. local offsetY = halfChordLength * (-deltaX / distance)
  70.  
  71. if clockwise then
  72. cx = midX + offsetX
  73. cy = midY + offsetY
  74. else
  75. cx = midX - offsetX
  76. cy = midY - offsetY
  77. end
  78. else
  79. error("Arc must have either I, J, or R parameters")
  80. end
  81.  
  82. return cx, cy
  83. end
  84.  
  85. function normalizeAngles(startAngle, endAngle, clockwise)
  86. msg{"Received startAngle %, endAngle % with direction %", startAngle, endAngle, clockwise}
  87.  
  88. -- Normalize angles to be within [0, 2*pi)
  89. local s = (startAngle + 2 * math.pi) % (2 * math.pi)
  90. local e = (endAngle + 2 * math.pi) % (2 * math.pi)
  91.  
  92. -- Add 2pi to start or end to ensure descending order for clockwise, ascending order for counterclockwise
  93. if clockwise and e >= s then
  94. s = s + 2 * math.pi
  95. elseif not clockwise and e <= s then
  96. e = e + 2 * math.pi
  97. end
  98.  
  99. msg{"Normalized Start angle: % radians / % degrees", s, math.deg(s)}
  100. msg{"Normalized End angle: % radians / % degrees", e, math.deg(e)}
  101.  
  102. return s, e
  103. end
  104.  
  105. function checkCrossings(startAngle, endAngle, clockwise)
  106. msg{"Checking for crossings for startAngle % to endAngle % with clockwise: %", startAngle, endAngle, clockwise}
  107. local crosses3Oclock = false
  108. local crosses9Oclock = false
  109.  
  110. if clockwise then
  111. -- Clockwise (G2) logic
  112. if startAngle > 2 * math.pi and endAngle < 2 * math.pi then
  113. crosses3Oclock = true
  114. end
  115. if startAngle > math.pi * 3 then
  116. crosses9Oclock = true
  117. elseif startAngle > math.pi and endAngle < math.pi then
  118. crosses9Oclock = true
  119. end
  120. else
  121. -- Counterclockwise (G3) logic
  122. if startAngle < 2 * math.pi and endAngle > 2 * math.pi then
  123. crosses3Oclock = true
  124. end
  125. if (startAngle < math.pi and endAngle > math.pi) or
  126. (startAngle > math.pi and endAngle > math.pi * 3) then
  127. crosses9Oclock = true
  128. end
  129. end
  130.  
  131. msg{"crosses3Oclock: % crosses9Oclock: %", crosses3Oclock, crosses9Oclock}
  132. return crosses3Oclock, crosses9Oclock
  133. end
  134.  
  135.  
  136. -- Function to split an arc if it crosses 3 o'clock or 9 o'clock
  137. function splitArc(command, params, nominalPos)
  138. msg{"Received arc for splitting: X: % Y: % Z: % I: % J: % R: % F: % Arc type: %", params.X, params.Y, params.Z, params.I, params.J, params.R, params.F, command}
  139. msg{"Nominal Position: X%, Y%, Z%", nominalPos.X, nominalPos.Y, nominalPos.Z}
  140. local result = {}
  141. local clockwise = command == "G2"
  142. local cx, cy = findArcCenter(params, nominalPos.X, nominalPos.Y, clockwise)
  143. msg{"Arc center: X=%, Y=%", cx, cy}
  144.  
  145. local startAngle = math.atan2(nominalPos.Y - cy, nominalPos.X - cx)
  146. local endAngle = math.atan2(params.Y - cy, params.X - cx)
  147.  
  148. -- Normalize angles
  149. startAngle, endAngle = normalizeAngles(startAngle, endAngle, clockwise)
  150.  
  151. -- Check for crossings
  152. local crosses3Oclock, crosses9Oclock = checkCrossings(startAngle, endAngle, clockwise)
  153.  
  154. -- Compute the radius if not provided
  155. local r = params.R
  156. if not r then
  157. r = math.sqrt((nominalPos.X - cx) ^ 2 + (nominalPos.Y - cy) ^ 2)
  158. end
  159. msg{"Arc Radius: %", r}
  160.  
  161. -- Determine if Z changes
  162. local zChanges = params.Z and params.Z ~= nominalPos.Z
  163.  
  164. -- Collect the crossing points
  165. local crossingPoints = {}
  166.  
  167. if crosses3Oclock then
  168. table.insert(crossingPoints, {angle = math.pi*2, x = cx + r, y = cy, label = "3 o'clock"})
  169. end
  170. if crosses9Oclock then
  171. table.insert(crossingPoints, {angle = math.pi, x = cx - r, y = cy, label = "9 o'clock"})
  172. end
  173.  
  174. -- Determine if start angle is in the top half of the circle
  175. local startAngle_isTop = (startAngle % (2 * math.pi)) <= math.pi
  176.  
  177. msg{"Clockwise is %. Start position in top half of the circle: % startAngle: %", clockwise, startAngle_isTop, startAngle % math.pi*2}
  178.  
  179. -- Sort the crossing points based on their angles using the booleans
  180. table.sort(crossingPoints, function(a, b)
  181. if clockwise then
  182. -- Clockwise logic
  183. if startAngle_isTop then
  184. return a.angle > b.angle -- 3 o'clock before 9 o'clock
  185. else
  186. return a.angle < b.angle -- 9 o'clock before 3 o'clock
  187. end
  188. else
  189. -- Counterclockwise logic
  190. if startAngle_isTop then
  191. return a.angle < b.angle -- 9 o'clock before 3 o'clock
  192. else
  193. return a.angle > b.angle -- 3 o'clock before 9 o'clock
  194. end
  195. end
  196. end)
  197.  
  198. for id, crossing in ipairs(crossingPoints) do
  199. msg{"Crossing %: %", id, crossing.angle}
  200. end
  201.  
  202. -- Split the arc at each crossing point
  203. local currentStartAngle = startAngle
  204. local currentStartX, currentStartY = nominalPos.X, nominalPos.Y
  205. local currentStartZ = nominalPos.Z
  206.  
  207. for _, crossing in ipairs(crossingPoints) do
  208. local splitAngle = crossing.angle
  209. local splitX = crossing.x
  210. local splitY = crossing.y
  211.  
  212. -- Calculate the portion of the Z movement up to this split point
  213. local totalAngleSpan = clockwise and (startAngle - endAngle) or (endAngle - startAngle)
  214. local angleToSplit = clockwise and (currentStartAngle - splitAngle) % (2 * math.pi) or (splitAngle - currentStartAngle) % (2 * math.pi)
  215. local angleFraction = math.abs(angleToSplit / totalAngleSpan)
  216. local splitZ = zChanges and (currentStartZ + (params.Z - nominalPos.Z) * angleFraction) or currentStartZ
  217.  
  218. -- Add this arc segment to the result table
  219. if not (splitX == currentStartX and splitY == currentStartY) then
  220. local arcParams = {X = splitX, Y = splitY, R = r}
  221. if zChanges then arcParams.Z = splitZ end
  222. table.insert(result, {command = command, params = arcParams})
  223. end
  224.  
  225. -- Update the start position for the next segment
  226. currentStartAngle = splitAngle
  227. currentStartX = splitX
  228. currentStartY = splitY
  229. currentStartZ = splitZ
  230. end
  231.  
  232. -- Add the final arc segment
  233. if not (currentStartX == params.X and currentStartY == params.Y) then
  234. local arcParams = {X = params.X, Y = params.Y, R = r}
  235. if zChanges then arcParams.Z = params.Z end
  236. table.insert(result, {command = command, params = arcParams})
  237. end
  238.  
  239. msg{"Returning % results", #result}
  240. return result
  241. end
  242.  
  243.  
  244. -- Function to parse a line of G-code
  245. function parseGCode(line)
  246. if line:match("^%s*$") then return "", "", {} end
  247.  
  248. -- Capture leading spaces and the rest of the line
  249. local leadingSpaces, rest = line:match("^(%s*)(%S+)%s*(.*)")
  250.  
  251. -- Calculate number of leading spaces
  252. local numLeadingSpaces = #leadingSpaces
  253.  
  254. -- Skip leading spaces and get the remaining part of the line
  255. local remainingLine = line:sub(numLeadingSpaces + 1)
  256.  
  257. -- Extract command (the first non-space segment)
  258. local command = remainingLine:match("^(%S+)")
  259.  
  260. -- Extract the parameters string (everything after the command)
  261. local paramsStr = remainingLine:sub(#command + 2) -- Adjust to remove command and following space
  262.  
  263. msg{"paramsStr before double negative replacement: %", paramsStr}
  264. -- Replace double negatives with positives
  265. paramsStr = string.gsub(paramsStr, "%-%-", "")
  266. msg{"paramsStr after double negative replacement: %", paramsStr}
  267.  
  268.  
  269. -- Initialize table for parameters
  270. local parsedParams = {}
  271.  
  272. -- Parse parameters
  273. for letter, value in paramsStr:gmatch("([XYZRIJKF])%s*([%d%.%+%-]+)") do
  274. parsedParams[letter] = tonumber(value)
  275. end
  276.  
  277. return leadingSpaces or "", command, parsedParams
  278. end
  279.  
  280. -- Function to process each move
  281. function processMove(leadingSpaces, command, params, pos, nominalPos)
  282. local newX = params.X
  283. local goingLeft
  284.  
  285. if newX then
  286. -- Determine if moving left based on the new position
  287. goingLeft = newX < nominalPos.X
  288. else
  289. -- If no new X is provided, retain the previous direction
  290. goingLeft = previouslyWentLeft
  291. end
  292.  
  293. msg{"Move: goingLeft: % newX: %", goingLeft, newX}
  294.  
  295. if newX then
  296. if goingLeft ~= previouslyWentLeft then
  297. local compensationMove = {X = nominalPos.X + (goingLeft and -backlash or 0)}
  298. msg{"Adding compensation move"}
  299. print(leadingSpaces .. formatGCode("G0", compensationMove))
  300. previouslyWentLeft = goingLeft
  301. end
  302. if goingLeft then
  303. params.X = newX - backlash
  304. end
  305. nominalPos.X = newX
  306. pos.X = params.X
  307. end
  308. if params.Y then
  309. nominalPos.Y = params.Y
  310. pos.Y = params.Y
  311. end
  312. if params.Z then
  313. nominalPos.Z = params.Z
  314. pos.Z = params.Z
  315. end
  316.  
  317. -- Print the move
  318. print(leadingSpaces .. formatGCode(command, params))
  319.  
  320. return pos, nominalPos
  321. end
  322.  
  323. -- Main function to process G-code file
  324. function compensateBacklash(inputFile)
  325. local file = io.open(inputFile, "r")
  326. if not file then
  327. error("Could not open file: " .. inputFile)
  328. end
  329.  
  330. local pos = {X = 0, Y = 0, Z = 0}
  331. local nominalPos = {X = 0, Y = 0, Z = 0}
  332.  
  333. print(string.format("(Warning, Compensated Toolpath!! X Axis Backlash: %s)", backlash))
  334.  
  335. for line in file:lines() do
  336. msg{"Processing line: %", line}
  337. msg{"Current position: %, %, % previouslyWentLeft: %", pos.X, pos.Y, pos.Z, previouslyWentLeft}
  338.  
  339. local leadingSpaces, command, params = parseGCode(line)
  340.  
  341. if command == "G2" or command == "G3" then
  342. local moves = splitArc(command, params, nominalPos)
  343. for _, move in ipairs(moves) do
  344. pos, nominalPos = processMove(leadingSpaces, move.command, move.params, pos, nominalPos)
  345. end
  346. elseif command == "G0" or command == "G1" then
  347. pos, nominalPos = processMove(leadingSpaces, command, params, pos, nominalPos)
  348. else
  349. print(line)
  350. end
  351. end
  352.  
  353. file:close()
  354. end
  355.  
  356. if not testing then compensateBacklash(inputFile) end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement