50_inst_per_sec.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import copy
  4. import math
  5. import os
  6. import sys
  7. import random
  8. from typing import Dict, List, Optional, Tuple
  9. # ====================================
  10. # Constants and Default Values
  11. # ====================================
  12. DEFAULT_BUFFER_FILLING_RATE_IN_C_PER_MS = 50.0 / 1000.0 # The buffer filling rate in #commands/ms
  13. DEFAULT_BUFFER_SIZE = 15 # The buffer size in #commands
  14. MINIMUM_PLANNER_SPEED = 0.05
  15. #Setting values for Ultimaker S5.
  16. MACHINE_MAX_FEEDRATE_X = 300
  17. MACHINE_MAX_FEEDRATE_Y = 300
  18. MACHINE_MAX_FEEDRATE_Z = 40
  19. MACHINE_MAX_FEEDRATE_E = 45
  20. MACHINE_MAX_ACCELERATION_X = 9000
  21. MACHINE_MAX_ACCELERATION_Y = 9000
  22. MACHINE_MAX_ACCELERATION_Z = 100
  23. MACHINE_MAX_ACCELERATION_E = 10000
  24. MACHINE_MAX_JERK_XY = 20
  25. MACHINE_MAX_JERK_Z = 0.4
  26. MACHINE_MAX_JERK_E = 5
  27. MACHINE_MINIMUM_FEEDRATE = 0
  28. MACHINE_ACCELERATION = 3000
  29. ## Gets the code and number from the given g-code line.
  30. def get_code_and_num(gcode_line: str) -> Tuple[str, str]:
  31. gcode_line = gcode_line.strip()
  32. cmd_code = gcode_line[0].upper()
  33. cmd_num = str(gcode_line[1:])
  34. return cmd_code, cmd_num
  35. ## Fetches arguments such as X1 Y2 Z3 from the given part list and returns a
  36. # dict.
  37. def get_value_dict(parts: List[str]) -> Dict[str, str]:
  38. value_dict = {}
  39. for p in parts:
  40. p = p.strip()
  41. if not p:
  42. continue
  43. code, num = get_code_and_num(p)
  44. value_dict[code] = num
  45. return value_dict
  46. # ============================
  47. # Math Functions - Begin
  48. # ============================
  49. def calc_distance(pos1, pos2):
  50. delta = {k: pos1[k] - pos2[k] for k in pos1}
  51. distance = 0
  52. for value in delta.values():
  53. distance += value ** 2
  54. distance = math.sqrt(distance)
  55. return distance
  56. ## Given the initial speed, the target speed, and the acceleration, calculate
  57. # the distance that's neede for the acceleration to finish.
  58. def calc_acceleration_distance(init_speed: float, target_speed: float, acceleration: float) -> float:
  59. if acceleration == 0:
  60. return 0.0
  61. return (target_speed ** 2 - init_speed ** 2) / (2 * acceleration)
  62. def calc_travel_time(p0, p1, init_speed: float, target_speed: float, acceleration: float) -> float:
  63. pass
  64. ## Calculates the point at which you must start braking.
  65. #
  66. # This gives the distance from the start of a line at which you must start
  67. # decelerating (at a rate of `-acceleration`) if you started at speed
  68. # `initial_feedrate` and accelerated until this point and want to end at the
  69. # `final_feedrate` after a total travel of `distance`. This can be used to
  70. # compute the intersection point between acceleration and deceleration in the
  71. # cases where the trapezoid has no plateau (i.e. never reaches maximum speed).
  72. def calc_intersection_distance(initial_feedrate: float, final_feedrate: float, acceleration: float, distance: float) -> float:
  73. if acceleration == 0:
  74. return 0
  75. return (2 * acceleration * distance - initial_feedrate * initial_feedrate + final_feedrate * final_feedrate) / (4 * acceleration)
  76. ## Calculates the maximum speed that is allowed at this point when you must be
  77. # able to reach target_velocity using the acceleration within the allotted
  78. # distance.
  79. def calc_max_allowable_speed(acceleration: float, target_velocity: float, distance: float) -> float:
  80. return math.sqrt(target_velocity * target_velocity - 2 * acceleration * distance)
  81. class Command:
  82. def __init__(self, cmd_str: str) -> None:
  83. self._cmd_str = cmd_str # type: str
  84. self._distance_in_mm = 0.0 # type float
  85. self._estimated_exec_time_in_ms = 0.0 # type: float
  86. self._cmd_process_function_map = {
  87. "G": self._handle_g,
  88. "M": self._handle_m,
  89. "T": self._handle_t,
  90. }
  91. self._is_comment = False # type: bool
  92. self._is_empty = False # type: bool
  93. #Fields taken from CuraEngine's implementation.
  94. self._recalculate = False
  95. self._accelerate_until = 0
  96. self._decelerate_after = 0
  97. self._initial_feedrate = 0
  98. self._final_feedrate = 0
  99. self._entry_speed = 0
  100. self._max_entry_speed =0
  101. self._nominal_length = False
  102. self._nominal_feedrate = 0
  103. self._max_travel = 0
  104. self._distance = 0
  105. self._acceleration = 0
  106. self._delta = [0, 0, 0]
  107. self._abs_delta = [0, 0, 0]
  108. ## Calculate the velocity-time trapezoid function for this move.
  109. #
  110. # Each move has a three-part function mapping time to velocity.
  111. def calculate_trapezoid(self, entry_factor, exit_factor):
  112. initial_feedrate = self._nominal_feedrate * entry_factor
  113. final_feedrate = self._nominal_feedrate * exit_factor
  114. #How far are we accelerating and how far are we decelerating?
  115. accelerate_distance = calc_acceleration_distance(initial_feedrate, self._nominal_feedrate, self._acceleration)
  116. decelerate_distance = calc_acceleration_distance(self._nominal_feedrate, final_feedrate, -self._acceleration)
  117. plateau_distance = self._distance - accelerate_distance - decelerate_distance #And how far in between at max speed?
  118. #Is the plateau negative size? That means no cruising, and we'll have to
  119. #use intersection_distance to calculate when to abort acceleration and
  120. #start braking in order to reach the final_rate exactly at the end of
  121. #this command.
  122. if plateau_distance < 0:
  123. accelerate_distance = calc_intersection_distance(initial_feedrate, final_feedrate, self._acceleration, self._distance)
  124. accelerate_distance = max(accelerate_distance, 0) #Due to rounding errors.
  125. accelerate_distance = min(accelerate_distance, self._distance)
  126. plateau_distance = 0
  127. self._accelerate_until = accelerate_distance
  128. self._decelerate_after = accelerate_distance + plateau_distance
  129. self._initial_feedrate = initial_feedrate
  130. self._final_feedrate = final_feedrate
  131. @property
  132. def is_command(self) -> bool:
  133. return not self._is_comment and not self._is_empty
  134. @property
  135. def estimated_exec_time_in_ms(self) -> float:
  136. return self._estimated_exec_time_in_ms
  137. def __str__(self) -> str:
  138. if self._is_comment or self._is_empty:
  139. return self._cmd_str
  140. distance_in_mm = round(self._distance_in_mm, 5)
  141. info = "d=%s t=%s" % (distance_in_mm, self._estimated_exec_time_in_ms)
  142. return self._cmd_str.strip() + " ; --- " + info + os.linesep
  143. ## Estimates the execution time of this command and calculates the state
  144. # after this command is executed.
  145. def parse(self) -> None:
  146. line = self._cmd_str.strip()
  147. if not line:
  148. self._is_empty = True
  149. return
  150. if line.startswith(";"):
  151. self._is_comment = True
  152. return
  153. # Remove comment
  154. line = line.split(";", 1)[0].strip()
  155. parts = line.split(" ")
  156. cmd_code, cmd_num = get_code_and_num(parts[0])
  157. cmd_num = int(cmd_num)
  158. func = self._cmd_process_function_map.get(cmd_code)
  159. if func is None:
  160. print("!!! no handle function for command type [%s]" % cmd_code)
  161. return
  162. func(cmd_num, parts)
  163. def _handle_g(self, cmd_num: int, parts: List[str]) -> None:
  164. estimated_exec_time_in_ms = 0.0
  165. # G0 and G1: Move
  166. if cmd_num in (0, 1):
  167. # Move
  168. distance = 0.0
  169. if len(parts) > 0:
  170. value_dict = get_value_dict(parts[1:])
  171. new_position = copy.deepcopy(buf.current_position)
  172. new_position[0] = value_dict.get("X", new_position[0])
  173. new_position[1] = value_dict.get("Y", new_position[1])
  174. new_position[2] = value_dict.get("Z", new_position[2])
  175. new_position[3] = value_dict.get("E", new_position[3])
  176. distance = calc_distance(buf.current_position, new_position)
  177. self._distance_in_mm = distance
  178. self._delta = [
  179. new_position[0] - buf.current_position[0],
  180. new_position[1] - buf.current_position[1],
  181. new_position[2] - buf.current_position[2],
  182. new_position[3] - buf.current_position[3]
  183. ]
  184. self._abs_delta = [abs(x) for x in self._delta]
  185. self._max_travel = max(self._abs_delta)
  186. if self._max_travel > 0:
  187. feedrate = buf.current_feedrate
  188. if "F" in value_dict:
  189. feedrate = value_dict["F"]
  190. if feedrate < MACHINE_MINIMUM_FEEDRATE:
  191. feedrate = MACHINE_MINIMUM_FEEDRATE
  192. self._nominal_feedrate = feedrate
  193. self._distance = math.sqrt(self._abs_delta[0] ** 2 + self._abs_delta[1] ** 2 + self._abs_delta[2] ** 2)
  194. if self._distance == 0:
  195. self._distance = self._abs_delta[3]
  196. current_feedrate = [d * feedrate / self._distance for d in self._delta]
  197. current_abs_feedrate = [abs(f) for f in current_feedrate]
  198. feedrate_factor = min(1.0, MACHINE_MAX_FEEDRATE_X)
  199. feedrate_factor = min(feedrate_factor, MACHINE_MAX_FEEDRATE_Y)
  200. feedrate_factor = min(feedrate_factor, buf.max_z_feedrate)
  201. feedrate_factor = min(feedrate_factor, MACHINE_MAX_FEEDRATE_E)
  202. #TODO: XY_FREQUENCY_LIMIT
  203. current_feedrate = [f * feedrate_factor for f in current_feedrate]
  204. current_abs_feedrate = [f * feedrate_factor for f in current_abs_feedrate]
  205. self._nominal_feedrate *= feedrate_factor
  206. self._acceleration = MACHINE_ACCELERATION
  207. max_accelerations = [MACHINE_MAX_ACCELERATION_X, MACHINE_MAX_ACCELERATION_Y, MACHINE_MAX_ACCELERATION_Z, MACHINE_MAX_ACCELERATION_E]
  208. for n in range(len(max_accelerations)):
  209. if self._acceleration * self._abs_delta[n] / self._distance > max_accelerations[n]:
  210. self._acceleration = max_accelerations[n]
  211. vmax_junction = MACHINE_MAX_JERK_XY / 2
  212. vmax_junction_factor = 1.0
  213. if current_abs_feedrate[2] > buf.max_z_jerk / 2:
  214. vmax_junction = min(vmax_junction, buf.max_z_jerk)
  215. if current_abs_feedrate[3] > buf.max_e_jerk / 2:
  216. vmax_junction = min(vmax_junction, buf.max_e_jerk)
  217. vmax_junction = min(vmax_junction, self._nominal_feedrate)
  218. safe_speed = vmax_junction
  219. if buf.previous_nominal_feedrate > 0.0001:
  220. xy_jerk = math.sqrt((current_feedrate[0] - buf.previous_feedrate[0]) ** 2 + (current_feedrate[1] - buf.previous_feedrate[1]) ** 2)
  221. vmax_junction = self._nominal_feedrate
  222. if xy_jerk > MACHINE_MAX_JERK_XY:
  223. vmax_junction_factor = MACHINE_MAX_JERK_XY / xy_jerk
  224. if abs(current_feedrate[2] - buf.previous_feedrate[2]) > MACHINE_MAX_JERK_Z:
  225. vmax_junction_factor = min(vmax_junction_factor, (MACHINE_MAX_JERK_Z / abs(current_feedrate[2] - buf.previous_feedrate[2])))
  226. if abs(current_feedrate[3] - buf.previous_feedrate[3]) > MACHINE_MAX_JERK_E:
  227. vmax_junction_factor = min(vmax_junction_factor, (MACHINE_MAX_JERK_E / abs(current_feedrate[3] - buf.previous_feedrate[3])))
  228. vmax_junction = min(buf.previous_nominal_feedrate, vmax_junction * vmax_junction_factor) #Limit speed to max previous speed.
  229. self._max_entry_speed = vmax_junction
  230. v_allowable = calc_max_allowable_speed(-self._acceleration, MINIMUM_PLANNER_SPEED, self._distance)
  231. self._entry_speed = min(vmax_junction, v_allowable)
  232. self._nominal_length = self._nominal_feedrate <= v_allowable
  233. self._recalculate = True
  234. buf.previous_feedrate = current_feedrate
  235. buf.previous_nominal_feedrate = self._nominal_feedrate
  236. buf.current_position = new_position
  237. self.calculate_trapezoid(self._entry_speed / self._nominal_feedrate, safe_speed / self._nominal_feedrate)
  238. travel_time_in_ms = -1 #Signal that we need to include this in our second pass.
  239. # G4: Dwell, pause the machine for a period of time. TODO
  240. if cmd_num == 4:
  241. # Pnnn is time to wait in milliseconds (P0 wait until all previous moves are finished)
  242. cmd, num = get_code_and_num(parts[1])
  243. num = float(num)
  244. if cmd == "P":
  245. if num > 0:
  246. estimated_exec_time_in_ms = num
  247. # G10: Retract. Assume 0.3 seconds for short retractions and 0.5 seconds for long retractions.
  248. if cmd_num == 10:
  249. # S0 is short retract (default), S1 is long retract
  250. is_short_retract = True
  251. if len(parts) > 1:
  252. cmd, num = get_code_and_num(parts[1])
  253. if cmd == "S" and num == 1:
  254. is_short_retract = False
  255. estimated_exec_time_in_ms = (0.3 if is_short_retract else 0.5) * 1000
  256. # G11: Unretract. Assume 0.5 seconds.
  257. if cmd_num == 11:
  258. estimated_exec_time_in_ms = 0.5 * 1000
  259. # G90: Set to absolute positioning. Assume 0 seconds.
  260. if cmd_num == 90:
  261. estimated_exec_time_in_ms = 0.0
  262. # G91: Set to relative positioning. Assume 0 seconds.
  263. if cmd_num == 91:
  264. estimated_exec_time_in_ms = 0.0
  265. # G92: Set position. Assume 0 seconds.
  266. if cmd_num == 92:
  267. estimated_exec_time_in_ms = 0.0
  268. # G280: Prime. Assume 10 seconds for using blob and 5 seconds for no blob.
  269. if cmd_num == 280:
  270. use_blob = True
  271. if len(parts) > 1:
  272. cmd, num = get_code_and_num(parts[1])
  273. if cmd == "S" and num == 1:
  274. use_blob = False
  275. estimated_exec_time_in_ms = (10.0 if use_blob else 5.0) * 1000
  276. # Update estimated execution time
  277. self._estimated_exec_time_in_ms = round(estimated_exec_time_in_ms, 5)
  278. def _handle_m(self, cmd_num: int, parts: List[str]) -> None:
  279. estimated_exec_time_in_ms = 0.0
  280. # M82: Set extruder to absolute mode. Assume 0 execution time.
  281. if cmd_num == 82:
  282. estimated_exec_time_in_ms = 0.0
  283. # M83: Set extruder to relative mode. Assume 0 execution time.
  284. if cmd_num == 83:
  285. estimated_exec_time_in_ms = 0.0
  286. # M104: Set extruder temperature (no wait). Assume 0 execution time.
  287. if cmd_num == 104:
  288. estimated_exec_time_in_ms = 0.0
  289. # M106: Set fan speed. Assume 0 execution time.
  290. if cmd_num == 106:
  291. estimated_exec_time_in_ms = 0.0
  292. # M107: Turn fan off. Assume 0 execution time.
  293. if cmd_num == 107:
  294. estimated_exec_time_in_ms = 0.0
  295. # M109: Set extruder temperature (wait). Uniformly random time between 30 - 90 seconds.
  296. if cmd_num == 109:
  297. estimated_exec_time_in_ms = random.uniform(30, 90) * 1000 # TODO: Check
  298. # M140: Set bed temperature (no wait). Assume 0 execution time.
  299. if cmd_num == 140:
  300. estimated_exec_time_in_ms = 0.0
  301. # M203: Set maximum feedrate. Only Z is supported. Assume 0 execution time.
  302. if cmd_num == 203:
  303. value_dict = get_value_dict(parts[1:])
  304. buf.max_z_feedrate = value_dict.get("Z", buf.max_z_feedrate)
  305. # M204: Set default acceleration. Assume 0 execution time.
  306. if cmd_num == 204:
  307. value_dict = get_value_dict(parts[1:])
  308. buf.acceleration = value_dict.get("S", buf.acceleration)
  309. estimated_exec_time_in_ms = 0.0
  310. # M205: Advanced settings, we only set jerks for Griffin. Assume 0 execution time.
  311. if cmd_num == 205:
  312. value_dict = get_value_dict(parts[1:])
  313. buf.max_xy_jerk = value_dict.get("XY", buf.max_xy_jerk)
  314. buf.max_z_jerk = value_dict.get("Z", buf.max_z_jerk)
  315. buf.max_e_jerk = value_dict.get("E", buf.max_e_jerk)
  316. estimated_exec_time_in_ms = 0.0
  317. self._estimated_exec_time_in_ms = estimated_exec_time_in_ms
  318. def _handle_t(self, cmd_num: int, parts: List[str]) -> None:
  319. # Tn: Switching extruder. Assume 2 seconds.
  320. estimated_exec_time_in_ms = 2.0
  321. self._estimated_exec_time_in_ms = estimated_exec_time_in_ms
  322. class CommandBuffer:
  323. def __init__(self, all_lines: List[str],
  324. buffer_filling_rate: float = DEFAULT_BUFFER_FILLING_RATE_IN_C_PER_MS,
  325. buffer_size: int = DEFAULT_BUFFER_SIZE
  326. ) -> None:
  327. self._all_lines = all_lines
  328. self._all_commands = list()
  329. self._buffer_filling_rate = buffer_filling_rate # type: float
  330. self._buffer_size = buffer_size # type: int
  331. self.acceleration = 3000
  332. self.current_position = [0, 0, 0, 0]
  333. self.current_feedrate = 0
  334. self.max_xy_jerk = MACHINE_MAX_JERK_XY
  335. self.max_z_jerk = MACHINE_MAX_JERK_Z
  336. self.max_e_jerk = MACHINE_MAX_JERK_E
  337. self.max_z_feedrate = MACHINE_MAX_FEEDRATE_Z
  338. # If the buffer can depletes less than this amount time, it can be filled up in time.
  339. lower_bound_buffer_depletion_time = self._buffer_size / self._buffer_filling_rate # type: float
  340. self._detection_time_frame = lower_bound_buffer_depletion_time
  341. self._code_count_limit = self._buffer_size
  342. self.previous_feedrate = [0, 0, 0, 0]
  343. self.previous_nominal_feedrate = 0
  344. print("Time Frame: %s" % self._detection_time_frame)
  345. print("Code Limit: %s" % self._code_count_limit)
  346. self._bad_frame_ranges = []
  347. def process(self) -> None:
  348. cmd0_idx = 0
  349. total_frame_time_in_ms = 0.0
  350. cmd_count = 0
  351. for idx, line in enumerate(self._all_lines):
  352. cmd = Command(line)
  353. cmd.parse()
  354. if not cmd.is_command:
  355. continue
  356. self._all_commands.append(cmd)
  357. #Second pass: Reverse kernel.
  358. kernel_commands = [None, None, None]
  359. for cmd in reversed(self._all_commands):
  360. if cmd.estimated_exec_time_in_ms >= 0:
  361. continue #Not a movement command.
  362. kernel_commands[2] = kernel_commands[1]
  363. kernel_commands[1] = kernel_commands[0]
  364. kernel_commands[0] = cmd
  365. self.reverse_pass_kernel(kernel_commands[0], kernel_commands[1], kernel_commands[2])
  366. #Third pass: Forward kernel.
  367. kernel_commands = [None, None, None]
  368. for cmd in self._all_commands:
  369. if cmd.estimated_exec_time_in_ms >= 0:
  370. continue #Not a movement command.
  371. kernel_commands[2] = kernel_commands[1]
  372. kernel_commands[1] = kernel_commands[0]
  373. kernel_commands[0] = cmd
  374. self.forward_pass_kernel(kernel_commands[0], kernel_commands[1], kernel_commands[2])
  375. self.forward_pass_kernel(kernel_commands[1], kernel_commands[2], None)
  376. #Fourth pass: Recalculate the commands that have _recalculate set.
  377. previous = None
  378. current = None
  379. for current in self._all_commands:
  380. if current.estimated_exec_time_in_ms >= 0:
  381. continue #Not a movement command.
  382. if previous:
  383. #Recalculate if current command entry or exit junction speed has changed.
  384. if previous._recalculate or current._recalculate:
  385. #Note: Entry and exit factors always >0 by all previous logic operators.
  386. previous.calculate_trapezoid(previous._entry_speed / previous._nominal_feedrate, current._entry_speed / previous._nominal_feedrate)
  387. previous._recalculate = False
  388. previous = current
  389. if current is not None:
  390. current.calculate_trapezoid(current._entry_speed / current._nominal_feedrate, MINIMUM_PLANNER_SPEED / current._nominal_feedrate)
  391. current._recalculate = False
  392. for idx, cmd in enumerate(self._all_commands):
  393. cmd_count += 1
  394. if idx > cmd0_idx or idx == 0:
  395. total_frame_time_in_ms += cmd.estimated_exec_time_in_ms
  396. if total_frame_time_in_ms > 1000.0:
  397. # Find the next starting command which makes the total execution time of the frame to be less than
  398. # 1 second.
  399. cmd0_idx += 1
  400. total_frame_time_in_ms -= self._all_commands[cmd0_idx].estimated_exec_time_in_ms
  401. cmd_count -= 1
  402. while total_frame_time_in_ms > 1000.0:
  403. cmd0_idx += 1
  404. total_frame_time_in_ms -= self._all_commands[cmd0_idx].estimated_exec_time_in_ms
  405. cmd_count -= 1
  406. # If within the current time frame the code count exceeds the limit, record that.
  407. if total_frame_time_in_ms <= self._detection_time_frame and cmd_count > self._code_count_limit:
  408. need_to_append = True
  409. if self._bad_frame_ranges:
  410. last_item = self._bad_frame_ranges[-1]
  411. if last_item["start_line"] == cmd0_idx:
  412. last_item["end_line"] = idx
  413. last_item["cmd_count"] = cmd_count
  414. last_item["time_in_ms"] = total_frame_time_in_ms
  415. need_to_append = False
  416. if need_to_append:
  417. self._bad_frame_ranges.append({"start_line": cmd0_idx,
  418. "end_line": idx,
  419. "cmd_count": cmd_count,
  420. "time_in_ms": total_frame_time_in_ms})
  421. def reverse_pass_kernel(self, previous: Optional[Command], current: Optional[Command], next: Optional[Command]) -> None:
  422. if not previous:
  423. return
  424. #If entry speed is already at the maximum entry speed, no need to
  425. #recheck. The command is cruising. If not, the command is in state of
  426. #acceleration or deceleration. Reset entry speed to maximum and check
  427. #for maximum allowable speed reductions to ensure maximum possible
  428. #planned speed.
  429. if current._entry_speed != current._max_entry_speed:
  430. #If nominal length is true, max junction speed is guaranteed to be
  431. #reached. Only compute for max allowable speed if block is
  432. #decelerating and nominal length is false.
  433. if not current._nominal_length and current._max_entry_speed > next._max_entry_speed:
  434. current._entry_speed = min(current._max_entry_speed, calc_max_allowable_speed(-current._acceleration, next._entry_speed, current._distance))
  435. else:
  436. current._entry_speed = current._max_entry_speed
  437. current._recalculate = True
  438. def forward_pass_kernel(self, previous: Optional[Command], current: Optional[Command], next: Optional[Command]) -> None:
  439. if not previous:
  440. return
  441. #If the previous command is an acceleration command, but it is not long
  442. #enough to complete the full speed change within the command, we need to
  443. #adjust the entry speed accordingly. Entry speeds have already been
  444. #reset, maximised and reverse planned by the reverse planner. If nominal
  445. #length is set, max junction speed is guaranteed to be reached. No need
  446. #to recheck.
  447. if not previous._nominal_length:
  448. if previous._entry_speed < current._entry_speed:
  449. entry_speed = min(current._entry_speed, calc_max_allowable_speed(-previous._acceleration, previous._entry_speed, previous._distance))
  450. if current._entry_speed != entry_speed:
  451. current._entry_speed = entry_speed
  452. current._recalculate = True
  453. def to_file(self, file_name: str) -> None:
  454. all_lines = [str(c) for c in self._all_commands]
  455. with open(file_name, "w", encoding = "utf-8") as f:
  456. f.writelines(all_lines)
  457. def report(self) -> None:
  458. for item in self._bad_frame_ranges:
  459. print("!!!!! potential bad frame from line %s to %s, code count = %s, in %s ms" % (
  460. item["start_line"], item["end_line"], item["cmd_count"], round(item["time_in_ms"], 4)))
  461. if __name__ == "__main__":
  462. if len(sys.argv) != 3:
  463. print("Usage: <input gcode> <output gcode>")
  464. sys.exit(1)
  465. in_filename = sys.argv[1]
  466. out_filename = sys.argv[2]
  467. with open(in_filename, "r", encoding = "utf-8") as f:
  468. all_lines = f.readlines()
  469. buf = CommandBuffer(all_lines)
  470. buf.process()
  471. buf.to_file(out_filename)
  472. buf.report()