GCodeToMakerbot.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. # Copyright (c) 2023 UltiMaker
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import json
  4. from typing import List, Dict, Tuple, Any, Optional, Union
  5. from math import pi
  6. from numpy import zeros, isfinite
  7. from numpy.linalg import norm
  8. from cura.CuraApplication import CuraApplication
  9. machine_lut = {"Makerbot Replicator mini": 'mini_8',
  10. "Makerbot Replicator 5th Gen": 'replicator_5',
  11. "Makerbot Replicator Z18": 'z18_6',
  12. "Makerbot Replicator+": 'replicator_b',
  13. "Makerbot Sketch": 'sketch',
  14. "Makerbot Sketch Large": 'sketch_large',
  15. "Makerbot Method": 'fire_e',
  16. "Makerbot Method X": 'lava_f',
  17. "Makerbot Method XL": 'magma_10'
  18. }
  19. extruder_lut = {"Makerbot Replicator mini": 'mk13',
  20. "Makerbot Replicator 5th Gen": 'mk13',
  21. "Makerbot Replicator Z18": 'mk13',
  22. "Makerbot Replicator+": 'mk13',
  23. "Makerbot Sketch": 'sketch_extruder',
  24. "Makerbot Sketch Large": 'sketch_l_extruder'
  25. }
  26. material_lut = {'2780b345-577b-4a24-a2c5-12e6aad3e690': 'abs',
  27. '88c8919c-6a09-471a-b7b6-e801263d862d': 'abs-wss1',
  28. '416eead4-0d8e-4f0b-8bfc-a91a519befa5': 'asa',
  29. '85bbae0e-938d-46fb-989f-c9b3689dc4f0': 'nylon-cf',
  30. '283d439a-3490-4481-920c-c51d8cdecf9c': 'nylon',
  31. '62414577-94d1-490d-b1e4-7ef3ec40db02': 'pc',
  32. '69386c85-5b6c-421a-bec5-aeb1fb33f060': 'pet', # PETG
  33. '0ff92885-617b-4144-a03c-9989872454bc': 'pla',
  34. 'a4255da2-cb2a-4042-be49-4a83957a2f9a': 'pva',
  35. 'a140ef8f-4f26-4e73-abe0-cfc29d6d1024': 'wss1',
  36. '77873465-83a9-4283-bc44-4e542b8eb3eb': 'sr30',
  37. '96fca5d9-0371-4516-9e96-8e8182677f3c': 'im-pla',
  38. '19baa6a9-94ff-478b-b4a1-8157b74358d2': 'tpu',
  39. }
  40. printhead_lut = {'1A': 'mk14',
  41. '1XA': 'mk14_hot',
  42. '1C': 'mk14_c',
  43. '2A': 'mk14_s',
  44. '2XA': 'mk14_hot_s',
  45. 'Lab': 'mk14_e',
  46. 'S+': 'mk13',
  47. 'EE': 'mk13_experimental',
  48. 'TS+': 'mk13_impla',
  49. 'SKT': 'sketch_extruder',
  50. }
  51. tags_lut = {"WALL-OUTER": ['Inset'], # 'Inset',
  52. "WALL-INNER": ['Inset'],
  53. "SKIN": ["Fill Roof Surface", "Roof"],
  54. "FILL": ['Infill', "Sparse"],
  55. "SUPPORT": ['Support'],
  56. "SUPPORT-INTERFACE": ['Support', 'Roof'],
  57. "PRIME-TOWER": ['Purge'],
  58. "SKIRT": ['Purge'],
  59. "TRAVEL": ['Travel Move'], # Note: these are not cura comments but will be calculated
  60. "RETRACT": ['Retract'],
  61. "UNRETRACT": ['Restart'],
  62. # 'Leaky Travel Move',
  63. # 'Long Retract',
  64. # 'Long Restart',
  65. # 'Trailing Extrusion Move'
  66. # 'Z Hop'
  67. # 'Un Z Hop'
  68. # 'Wipe Extruder'
  69. # 'Raft'
  70. # 'Fill Roof Surface'
  71. # 'Floor'
  72. # 'Wait for Temperature'
  73. # 'Quick Toggle'
  74. # 'Solid'
  75. # 'Sparse'
  76. }
  77. bead_lut = {"WALL-OUTER": 'BeadMode External',
  78. "SKIN": 'BeadMode External',
  79. "WALL-INNER": 'BeadMode Internal Thick',
  80. "FILL": 'BeadMode User3',
  81. "SUPPORT": 'BeadMode Internal Thick',
  82. "SUPPORT-INTERFACE": 'BeadMode External',
  83. "PRIME-TOWER": 'BeadMode User1',
  84. "SKIRT": 'BeadMode User1',
  85. "RAFT": 'BeadMode User1',
  86. }
  87. def move(x: float, y: float, z: float, a: float, b: float, feedrate: float, tags: Optional[List[str]] = None) -> Dict[
  88. str, Any]:
  89. """ Create a move (G0 or G1) in Makerbot json format
  90. The 0,0 point is in the middle of the build plate iso of the left front.
  91. This does not look nice in Cura because the xyz coordinate system will be displayed in the middle of the print.
  92. Therefore, we pretent this system has a coordinate 0,0 in the left front (setting) and offset the coordinates
  93. to the center at x,y = 75,95
  94. """
  95. if tags is None:
  96. tags = []
  97. return {"command": {'function': "move",
  98. "metadata": {"relative": {"a": True, "b": True, "x": False, "y": False, "z": False}},
  99. "parameters": {"a": a,
  100. "b": b,
  101. "feedrate": feedrate,
  102. "x": x, # x - 75,
  103. "y": y, # y - 95,
  104. "z": z},
  105. "tags": tags}}
  106. def comment(comment: str, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  107. """ Create a comment (metadata) in Makerbot json format
  108. Mainly used to create layer section information to display in the viewer
  109. """
  110. if tags is None:
  111. tags = []
  112. return {"command": {"function": "comment",
  113. "metadata": {},
  114. "parameters": {"comment": f"{comment}"},
  115. "tags": tags}}
  116. def set_toolhead_temperature(index: int, temperature: float, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  117. """ Set the print head temperature setpoint (M104 or M109) in Makerbot json format """
  118. if tags is None:
  119. tags = []
  120. return {"command": {"function": "set_toolhead_temperature",
  121. "metadata": {},
  122. "parameters": {"index": index, "temperature": temperature},
  123. "tags": tags}}
  124. def wait_for_temperature(index: int, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  125. """ Wait for the print head temperature to reach its setpoint (M109) in Makerbot json format """
  126. if tags is None:
  127. tags = []
  128. return {"command": {"function": "wait_for_temperature",
  129. "metadata": {},
  130. "parameters": {"index": index},
  131. "tags": tags}}
  132. def change_toolhead(index: int, x: float, y: float, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  133. """ Switch tool (T0 or T1) in Makerbot json format """
  134. if tags is None:
  135. tags = []
  136. return {"command": {"function": "change_toolhead",
  137. "metadata": {},
  138. "parameters": {"index": index, "x": x, "y": y},
  139. "tags": tags},
  140. }
  141. def toggle_fan(index: int, value: bool, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  142. """ Toggle the cold end/heat break cooling fans per print head.
  143. Note that these are NOT the object cooling fans (blowing onto the print).
  144. Makerbot has a cold end fan per print head and switches the cold end cooling on and off during tool switches
  145. No corresponding GCode exists as far as I know.
  146. """
  147. if tags is None:
  148. tags = []
  149. # Please note that these are the heatbreak cooling fans, not the object cooling
  150. return {"command": {"function": "toggle_fan",
  151. "metadata": {},
  152. "parameters": {"index": index, "value": value},
  153. "tags": tags}}
  154. def fan_duty(index: int, value: float, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  155. """ Set object cooling fan speed (M106, M107) in Makerbot json format """
  156. if tags is None:
  157. tags = []
  158. # These are the object cooling fans
  159. return {"command": {"function": "fan_duty",
  160. "metadata": {},
  161. "parameters": {"index": index, "value": value},
  162. "tags": tags}}
  163. def delay(seconds: float, tags: Optional[List[str]] = None) -> Dict[str, Any]:
  164. """ Pause (G4) in Makerbot json format """
  165. if tags is None:
  166. tags = []
  167. return {"command": {"function":
  168. "delay", "metadata": {},
  169. "parameters": {"seconds": seconds},
  170. "tags": tags}}
  171. def _incrementOrStartCount(metadata: Dict, key: str) -> None:
  172. if key in metadata:
  173. metadata[key] = metadata[key] + 1
  174. else:
  175. metadata[key] = 1
  176. def convert(gcode: str) -> Tuple[str, Dict]:
  177. """
  178. The prime tower middle x,y position is used to heatup the print heads during a print head switch
  179. Convert a given gcode text string into:
  180. - a output command string in json or adjusted gcode (depending on Sketch or Method).
  181. - a meta data dictionary
  182. - a file name for the generated output commands
  183. """
  184. metadata = {}
  185. metadata["platform_temperature"] = 0 # FIXME! Always 0 for Methods. Those just have a build-volume temp.
  186. commands: List[Dict[str, Any]] = []
  187. section: List[Union[Dict[str, Any], str]] = [] # List of commands
  188. tool_nr = 0 # Current active toolhead 0 or 1
  189. layer_nr = 0
  190. section_nr = 1
  191. curr_low_pos = 10000
  192. curr_high_pos = -10000
  193. old_low_pos = 0.0
  194. old_high_pos = 0.0
  195. speed = 5.0 # mm/sec
  196. fan_on = [False, False]
  197. fan_speed = 1.0
  198. temperature = 0
  199. pos = zeros(4) # x,y,z,e
  200. last_pos = zeros(4) # x,y,z,e
  201. line_type = 'TRAVEL'
  202. volume = [0, 0]
  203. temperatures = [0, 0]
  204. materials = ["", ""]
  205. printheads = ["", ""]
  206. dual_extrusion = False
  207. bot = ""
  208. result = ""
  209. application = CuraApplication.getInstance()
  210. machine_manager = application.getMachineManager()
  211. global_stack = machine_manager.activeMachine
  212. prime_tower_x = global_stack.getProperty("prime_tower_position_x", "value")
  213. prime_tower_y = global_stack.getProperty("prime_tower_position_y", "value")
  214. prime_tower_size = global_stack.getProperty("prime_tower_size", "value")
  215. # Logger.log("d", f"Prime tower: ({prime_tower_x},{prime_tower_y}) and size {prime_tower_size}")
  216. prime_tower_x = prime_tower_x - prime_tower_size / 2 # we need the prime tower middle point
  217. prime_tower_y = prime_tower_y + prime_tower_size / 2 # see engine source code
  218. # Logger.log("i","Converting ufp to makerbot format")
  219. for line in gcode.splitlines(): # split gcode lines and loop through them
  220. _incrementOrStartCount(metadata, "total_commands")
  221. if line.startswith(';'): # Line is a comment, extract info
  222. if line.startswith(";LAYER:"): # Track the layer number
  223. _incrementOrStartCount(metadata, "num_z_layers")
  224. layer_nr = int(line.split(';LAYER:')[1].strip())
  225. elif line.startswith(";TYPE:"): # Track the line type, = end of a layer section
  226. line_type = line[6:].strip()
  227. if curr_high_pos == -10000: # keep track of z heights within this layer section
  228. curr_low_pos = old_low_pos
  229. curr_high_pos = old_high_pos
  230. old_low_pos = curr_low_pos
  231. old_high_pos = curr_high_pos
  232. section = add_section_json(section,
  233. tool_nr=tool_nr,
  234. section_nr=section_nr,
  235. low_pos=curr_low_pos,
  236. high_pos=curr_high_pos)
  237. commands.extend(section)
  238. section = [] # start new layer section
  239. section_nr += 1
  240. curr_low_pos = 10000
  241. curr_high_pos = -10000
  242. elif line.startswith(";TARGET_MACHINE.NAME:"):
  243. bot = line.replace(";TARGET_MACHINE.NAME:", "").strip()
  244. gcode_bot = "Sketch" in bot # these machine read gcode
  245. elif line.startswith(";PRINT.TIME:"):
  246. duration = float(line.replace(";PRINT.TIME:", ""))
  247. elif line.startswith(";PRINT.SIZE.MIN.X:"):
  248. pass
  249. elif line.startswith(";PRINT.SIZE.MAX.X:"):
  250. pass
  251. elif line.startswith(";PRINT.SIZE.MIN.Y:"):
  252. pass
  253. elif line.startswith(";PRINT.SIZE.MAX.Y:"):
  254. pass
  255. elif line.startswith(";PRINT.SIZE.MIN.Z:"):
  256. pass
  257. elif line.startswith(";PRINT.SIZE.MAX.Z:"):
  258. pass
  259. elif line.startswith(";EXTRUDER_TRAIN.0.MATERIAL.VOLUME_USED:"):
  260. volume[0] = float(line.replace(";EXTRUDER_TRAIN.0.MATERIAL.VOLUME_USED:", ""))
  261. elif line.startswith(";EXTRUDER_TRAIN.1.MATERIAL.VOLUME_USED:"):
  262. volume[1] = float(line.replace(";EXTRUDER_TRAIN.1.MATERIAL.VOLUME_USED:", ""))
  263. if volume[1] > 0:
  264. dual_extrusion = True
  265. elif line.startswith(";BUILD_PLATE.INITIAL_TEMPERATURE:"):
  266. # Build plate temperature (assumed for now, it seems that their files only set the chamber temperature)
  267. pass
  268. elif line.startswith(";BUILD_VOLUME.TEMPERATURE:"):
  269. pass
  270. elif line.startswith(";EXTRUDER_TRAIN.0.INITIAL_TEMPERATURE:"):
  271. temperatures[0] = int(line.replace(";EXTRUDER_TRAIN.0.INITIAL_TEMPERATURE:", ""))
  272. elif line.startswith(";EXTRUDER_TRAIN.1.INITIAL_TEMPERATURE:"):
  273. temperatures[1] = int(line.replace(";EXTRUDER_TRAIN.1.INITIAL_TEMPERATURE:", ""))
  274. elif line.startswith(";EXTRUDER_TRAIN.0.MATERIAL.GUID:"):
  275. materials[0] = material_lut.get(line.replace(";EXTRUDER_TRAIN.0.MATERIAL.GUID:", "").strip())
  276. elif line.startswith(";EXTRUDER_TRAIN.1.MATERIAL.GUID:"):
  277. materials[1] = material_lut.get(line.replace(";EXTRUDER_TRAIN.1.MATERIAL.GUID:", "").strip())
  278. elif line.startswith(";EXTRUDER_TRAIN.0.NOZZLE.NAME:"):
  279. ph = line.replace(";EXTRUDER_TRAIN.0.NOZZLE.NAME:", "").strip()
  280. if ph in printhead_lut:
  281. printheads[0] = printhead_lut[ph]
  282. elif line.startswith(";EXTRUDER_TRAIN.1.NOZZLE.NAME:"):
  283. ph = line.replace(";EXTRUDER_TRAIN.1.NOZZLE.NAME:", "").strip()
  284. if ph in printhead_lut:
  285. printheads[1] = printhead_lut[ph]
  286. elif line.startswith('G0 ') or line.startswith('G1 '): # Move
  287. # Logger.log("i", line)
  288. pos, speed, a, b, tags = analyse_move(line, pos, speed, last_pos, tool_nr, layer_nr, line_type)
  289. if last_pos[2] != pos[2]:
  290. _incrementOrStartCount(metadata, "num_z_transitions")
  291. last_pos = pos.copy()
  292. curr_high_pos = max(curr_high_pos, pos[2])
  293. curr_low_pos = min(curr_low_pos, pos[2])
  294. section.append(move(x=pos[0], y=pos[1], z=pos[2], a=a, b=b, feedrate=speed, tags=tags))
  295. else: # no comments and no move = other commands
  296. if line.startswith('G92 E0'): # Reset extrusion channel
  297. pos[3] = 0.0
  298. last_pos[3] = 0.0
  299. elif line.startswith('T'): # toolswitch. todo: check if this logic is OK
  300. """ Switch sequence seems to be:
  301. Set soon to be deactivated print head to standby temperature
  302. Object cooling to full power (100%) for this print head to prevent oozing
  303. Set print temperature of soon to become active print head
  304. z hop 0.4mm at 10mm/s
  305. Move in x direction at 500mm/s to <unknown> x position
  306. un z hop 0.4mm at 10mm/s
  307. change tool head (I guess it now bumps the side wall) with wait for temperature position
  308. Move to wait for temperature (prime tower middle) position with 500mm/s
  309. Wait for temperature of new print core to have reached print temp.
  310. Wait an extra 5 seconds
  311. Toggle object cooling fans off for the deactivated print head
  312. Toggle object cooling in for the activated print head
  313. Set fan duty cycle back to normal value (thus not 100%)
  314. """
  315. _incrementOrStartCount(metadata, "num_tool_changes")
  316. index = int(line[1:].strip())
  317. if tool_nr != index:
  318. tool_nr = index # New active tool
  319. old_active_tool_nr = (tool_nr + 1) % 2 # Tool to be deactiviated
  320. # temperatures are already set by Cura before the print head switch
  321. # set fan at full speed (cooling soon to be inactive head to prevent oozing)
  322. section.append(fan_duty(index=old_active_tool_nr, value=1.0))
  323. # zhop 0.4mm with 10mm/s
  324. section.append(
  325. move(x=last_pos[0], y=last_pos[1], z=last_pos[2] + 0.4, a=0, b=0, feedrate=10, tags=['Z hop']))
  326. # move to side with 250mm/s
  327. section.append(move(x=last_pos[0], y=last_pos[1], z=last_pos[2] + 0.4, a=0, b=0, feedrate=250,
  328. tags=["Quick Toggle"]))
  329. # unhop -0.4mm with 10mm/s
  330. # section.append(move(x=prime_tower_x, y=last_pos[1], z=last_pos[2], a=0, b=0, feedrate=10, tags=["Un Z hop"]))
  331. # section.append(move(x=prime_tower_x, y=last_pos[1], z=last_pos[2], a=0, b=0, feedrate=250, tags=["Un Z hop"])) # set last speed to 250
  332. # change tool and move to wait position (center of prime tower)
  333. section.append(change_toolhead(index=index, x=prime_tower_x, y=prime_tower_y, tags=["Travel Move"]))
  334. section.append(wait_for_temperature(index=tool_nr))
  335. section.append(delay(5))
  336. # Swap object cooling fan and set correct fan speed on new tool
  337. if fan_on[
  338. old_active_tool_nr]: # Turn off the cold end cooling fan of the print head that will become active
  339. fan_on[old_active_tool_nr] = False
  340. section.append(toggle_fan(index=old_active_tool_nr, value=False))
  341. if not fan_on[tool_nr]:
  342. fan_on[tool_nr] = True
  343. section.append(toggle_fan(index=tool_nr, value=True))
  344. section.append(fan_duty(index=tool_nr, value=fan_speed))
  345. # Move back to the edge of the prime tower
  346. section.append(move(x=last_pos[0], y=last_pos[1], z=last_pos[2] + 0.4, a=0, b=0, feedrate=100,
  347. tags=['Wait for Temperature', "Travel Move"]))
  348. section.append(move(x=last_pos[0], y=last_pos[1], z=last_pos[2], a=0, b=0, feedrate=10,
  349. tags=["Un Z hop"]))
  350. elif line.split(" ")[0] in ('M104', 'M109'): # Set hot end temperature
  351. parts = line.split()
  352. if parts[1].startswith('T'):
  353. index = int(parts[1][1:].strip())
  354. temperature = int(float(parts[2][1:]))
  355. else:
  356. index = tool_nr
  357. temperature = int(float(parts[1][1:])) # remove S
  358. section.append(set_toolhead_temperature(index=index, temperature=temperature))
  359. if line.startswith('M109'): # Wait for temperature to be reached
  360. section.append(wait_for_temperature(index=index))
  361. # if fan_on[0]: # Turn off the coldend cooling fans
  362. # fan_on[0] = False
  363. # section.append(toggle_fan(index=0, value=False))
  364. # if fan_on[1]:
  365. # fan_on[1] = False
  366. # section.append(toggle_fan(index=1, value=False))
  367. elif line.startswith('M106'): # Set object cooling fan speed
  368. parts = line.split()
  369. fan_speed = float(parts[1][1:]) / 255.0
  370. section.append(fan_duty(index=tool_nr, value=fan_speed))
  371. elif line.startswith('M107'): # turn off fans
  372. section.append(fan_duty(index=0, value=0))
  373. section.append(fan_duty(index=1, value=0))
  374. elif line.startswith('G4'): # dwell/delay
  375. seconds = float(line.split()[1][1:])
  376. section.append(delay(seconds=seconds))
  377. # Write last layer section and create the result string
  378. section = add_section_json(section,
  379. tool_nr=tool_nr,
  380. section_nr=section_nr,
  381. low_pos=curr_low_pos,
  382. high_pos=curr_high_pos)
  383. commands.extend(section)
  384. result = "[\n" + ",\n".join([json.dumps(command) for command in commands]) + "\n]"
  385. density = 1.2 # todo get from material profiles
  386. area = pi * (1.75 / 2) ** 2
  387. # Logger.log("i", "Converting mesh to makerbot format: done. Adding meta data")
  388. if "Method" not in bot: # single print head bots, remove 2nd extruder metadata
  389. materials.pop()
  390. temperatures.pop()
  391. volume.pop()
  392. printheads.pop()
  393. printheads[0] = extruder_lut[bot]
  394. else:
  395. pass
  396. return result, metadata
  397. def analyse_move(line: str, pos, speed, last_pos, tool_nr, layer_nr, line_type) -> Tuple[
  398. List[float], float, float, float, List[str]]:
  399. """ Analyse a G0 or G1 move and extract the X,Y,Z,E and speed and tag data from it
  400. """
  401. parts = line.split()
  402. for part in parts:
  403. segment = part[0]
  404. if segment == "X":
  405. pos[0] = float(part[1:])
  406. elif segment == "Y":
  407. pos[1] = float(part[1:])
  408. elif segment == "Z":
  409. pos[2] = float(part[1:])
  410. elif segment == "E":
  411. pos[3] = float(part[1:])
  412. elif segment == "F":
  413. speed = float(part[1:]) / 60 # From mm/min to mm/sec
  414. delta = pos - last_pos
  415. dist = norm(delta[:3]) # Segment length [mm]
  416. if tool_nr == 0:
  417. a, b = delta[3], 0
  418. else:
  419. a, b = 0, delta[3]
  420. tags = []
  421. t = max(dist / speed, abs(delta[3]) / speed) # Time spend in this segment (acc./dec. neglected!) [sec]
  422. # NOTE: For future improvements we might want to use the Marlin planner as it is implemented in the GCodeAnalyzer:
  423. # https://github.com/Ultimaker/GCodeAnalyzer/blob/main/GCodeAnalyzer/planner/marlin.py
  424. # to determine the time; which would take into account acc./dec. and jerk.
  425. if isfinite(t) and t > 0.0:
  426. if delta[3] == 0.0 and dist > 0.0:
  427. tags.extend(tags_lut["TRAVEL"]) # TRAVEL move
  428. elif delta[3] < 0.0:
  429. tags.extend(tags_lut["RETRACT"]) # RETRACT move
  430. elif delta[3] > 0.0 and dist == 0.0:
  431. tags.extend(tags_lut["UNRETRACT"]) # UNRETRACT move
  432. else:
  433. tags.extend(tags_lut[line_type])
  434. if layer_nr < 0:
  435. tags.extend(['Raft'])
  436. return pos, speed, a, b, tags
  437. def add_section_gcode(section: List[str], low_pos: float, high_pos: float, section_nr: int, result: str) -> str:
  438. """ Add a layer section to a gcode file (for Sketch machines)"""
  439. section.insert(0, "; Width 1;")
  440. section.insert(0, f"; Thickness {high_pos - low_pos};")
  441. section.insert(0, f"; Upper Position {high_pos};")
  442. section.insert(0, f"; Lower Position {low_pos};")
  443. section.insert(0, f"; Material 0;")
  444. section.insert(0, f"; Layer Section {section_nr} ({section_nr});") # TODO: Find out with these '()' numbers really mean, assume they maybe need to be unique.
  445. section.insert(0, "; Update Progress;")
  446. return result + '\n' + '\n'.join(section)
  447. def add_section_json(section: List[Dict[str, Any]], tool_nr: int, section_nr: int,
  448. low_pos: float, high_pos: float) -> Tuple[List[Dict[str, Any]], float, float]:
  449. """
  450. Add a layer section to json print file (Method and Replicator machines)
  451. Example for the json machines:
  452. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Layer Section 11 (4)"}, "tags": []}
  453. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Material 1"}, "tags": []}
  454. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Lower Position 1"}, "tags": []}
  455. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Upper Position 1.27"}, "tags": []}
  456. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Thickness 0.27"}, "tags": []}
  457. "command": {"function": "comment", "metadata": {}, "parameters": {"comment": "Width 0.4"}, "tags": []}
  458. """
  459. section.insert(0, comment(f"Width 0.4"))
  460. section.insert(0, comment(f"Thickness {high_pos - low_pos}"))
  461. section.insert(0, comment(f"Upper Position {high_pos}"))
  462. section.insert(0, comment(f"Lower Position {low_pos}")) # Todo better fall back if position is unknown
  463. section.insert(0, comment(f"Material {tool_nr}"))
  464. section.insert(0, comment(f"Layer Section {section_nr} ({section_nr})")) # TODO: Find out with these '()' numbers really mean, assume they maybe need to be unique.
  465. return section