src.acoustools.Fabrication.Translator
Translates gcode file to lcode file
1''' 2Translates gcode file to lcode file 3 4''' 5# NEED TO ADD FRAME RATE CONTROL 6 7 8from acoustools.Utilities import create_points 9from acoustools.Paths import interpolate_points, distance, interpolate_arc, interpolate_bezier, bezier_to_distance 10 11from torch import Tensor 12import torch 13from typing import Literal 14 15 16def gcode_to_lcode(fname:str, output_name:str|None=None, output_dir:str|None=None, log:bool=True, log_name:str|None=None, log_dir:str=None, 17 divider:float = 1000, relative:bool = False, 18 max_stepsize:float=0.001, extruder:Tensor|None = None, pre_print_command:str = '', 19 post_print_command:str = '', print_lines:bool=False, pre_commands:str= '', post_commands:str='', 20 use_BEM:bool = False, sig_type:str='Trap', travel_type:Literal["hypot","legsXY","legsZ","bezier"]='hypot', 21 add_optimisation_commands:bool=True, via:Tensor|None=None, use_functions:bool=False): 22 ''' 23 Converts a .gcode file to a .lcode file \n 24 ```Python 25 from acoustools.Fabrication.Translater import gcode_to_lcode 26 27 28 pth = 'acoustools/tests/data/gcode/rectangle.gcode' 29 30 pre_cmd = 'C0;\\n' 31 post_cmd = 'C1;\\nC3:10;\\nC2;\\n' 32 gcode_to_lcode(pth, pre_print_command=pre_cmd, post_print_command=post_cmd) 33 34 ``` 35 :param fname: The file name of the gcode file 36 :param output_name: The filename for the lcode file, if None will use the gcode file name with .gcode replaced with .lcode 37 :param output_dir: output directory of the lcode file, if None will use the same as the gcode file 38 :param log: If True will save log files 39 :param log_name: Name for the log file, if None will will use the gcode file name with .gcode replaced with .txt and the name with '_log' appended 40 :param log_dir: Directory for log file, if None will use same as the gcode file 41 :param divider: Value to divide dx,dy,dz by - useful to change units 42 :param relative: If true will change relative to last position, else will be absolute 43 :param max_stepsize: Maximum stepsize allowed, default 1mm 44 :param extruder: Extruder location, if None will use (0,0.10, 0) 45 :param pre_print_command: commands to put before the block resulting from each G01, G02 and G03 command 46 :param post_print_command: commands to put before the block resulting from each G01, G02 and G03 command 47 :param print_lines: If true will print which line is being processed 48 :param pre_commands: commands to put at the top of the file 49 :param post_commands: commands to put at the end of the file 50 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 51 :param via: A point to move all paths through before going to the end point 52 :param use_functions: If true will collapse common code into functions - needs `via` to be specified too 53 :param use_BEM: If True will use scattering for the mesh while it is built 54 :param sig_type: Defined signature to use for traps 55 :param travel_type: Way to move the particle 56 57 ''' 58 59 60 name = fname.replace('.gcode','') 61 parts = name.split('/') 62 name = parts[-1] 63 path = '/'.join(parts[:-1]) 64 65 if extruder is None: 66 extruder = create_points(1,1,0,-0.04, 0.04) 67 68 if output_name is None: 69 output_name = name 70 71 if output_dir is None: 72 output_dir = path 73 74 if log_dir is None: 75 log_dir = output_dir 76 77 if log_name is None: 78 log_name = name 79 80 if use_functions: 81 functions = {} 82 else: 83 functions = None 84 85 86 output_file = open(output_dir+'/'+output_name+'.lcode','w') 87 output_file.write(pre_commands) 88 89 if log: log_file = open(log_dir+'/'+log_name+'_log.txt','w') 90 91 head_position = create_points(1,1,0,0,0) 92 E_val = 0 93 94 95 96 with open(fname) as file: 97 lines = file.readlines() 98 Nl = len(lines) 99 for i,line in enumerate(lines): 100 if print_lines and i % 10 == 0: print(f'Computing, {i}/{Nl}', end='\r') 101 line = line.rstrip() 102 line_split = line.split() 103 if len(line_split) == 0 or line.startswith(';'): 104 if log: log_file.write(f'Line {i+1}, Ignoring line {line})\n') 105 continue 106 code = line_split[0] 107 args = line_split[1:] 108 command = '' 109 110 E_val = get_E_val(*args, E_val=E_val) 111 112 if (code == 'G00' or code == 'G0') or ((code == 'G01' or code == 'G1') and E_val == 0): #Non-fabricating move 113 head_z = head_position[:,2].item() 114 command, head_position = convert_G00(*args, head_position=head_position, divider=divider, relative=relative) 115 if use_BEM and head_z != head_position[:,2]: 116 command += 'C10;\n' 117 118 if log: log_file.write(f'Line {i+1}, G00 Command: Virtual head updated to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} ({line}), E value set to {E_val} \n') 119 elif code == 'G01' or code == 'G1': #Fabricating move 120 command, head_position, N = convert_G01(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 121 max_stepsize=max_stepsize,pre_print_command=pre_print_command, 122 post_print_command=post_print_command, sig=sig_type, travel_type=travel_type, via=via, 123 functions=functions) 124 125 if log: log_file.write(f'Line {i+1}, G01 Command: Line printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}), E value set to {E_val} \n') 126 127 elif code == 'G02' or code == 'G2': #Fabricating move 128 command, head_position, N = convert_G02_G03(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 129 max_stepsize=max_stepsize, anticlockwise=False,pre_print_command=pre_print_command, 130 post_print_command=post_print_command, sig=sig_type, 131 travel_type=travel_type, add_optimisation_commands=add_optimisation_commands , via=via, 132 functions=functions) 133 134 if log: log_file.write(f'Line {i+1}, G02 Command: Circle printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}) \n') 135 136 elif code == 'G03' or code == 'G3': #Fabricating arc 137 command, head_position, N = convert_G02_G03(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 138 max_stepsize=max_stepsize, anticlockwise=True,pre_print_command=pre_print_command, 139 post_print_command=post_print_command, sig=sig_type, 140 travel_type=travel_type , add_optimisation_commands=add_optimisation_commands , via=via, 141 functions=functions) 142 143 if log: log_file.write(f'Line {i+1}, G03 Command: Circle printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}) \n') 144 elif code == 'G04' or code == 'G4': #Dwell!! 145 pass 146 elif code.startswith(';'): 147 if log: log_file.write(f'Line {i+1}, Ignoring Comment ({line})\n') 148 149 else: #Ignore everything else 150 if log: log_file.write(f'Line {i+1}, Ignoring code {code} ({line})\n') 151 152 output_file.write(command) 153 154 155 output_file.write(post_commands) 156 output_file.close() 157 if print_lines: print() 158 if log: log_file.close() 159 160def get_E_val(*args:str, E_val:float): 161 E = E_val 162 for arg in args: 163 if arg.startswith('E'): 164 E = float(arg[1:]) 165 return E 166 167def parse_xyz(*args:str): 168 ''' 169 Takes the args from a gcode line and ginds the XYZ arguments \n 170 :param args: list of arguments from gcode (all of the line but the command) 171 ''' 172 x,y,z = None,None,None #Need to check if any of these need to not be changed 173 for arg in args: 174 arg = arg.lower() 175 if 'x' in arg: 176 x = float(arg.replace('x','')) 177 178 if 'y' in arg: 179 y = float(arg.replace('y','')) 180 181 if 'z' in arg: 182 z = float(arg.replace('z','')) 183 184 return x,y,z 185 186def update_head(head_position: Tensor, dx:float, dy:float, dz:float, divider:float, relative:bool): 187 ''' 188 Updates the head (or other tensor) based on dx,dy,dz \n 189 :param head_position: Start position 190 :param dx: change (or new value) for x axis 191 :param dy: change (or new value) for y axis 192 :param dz: change (or new value) for z axis 193 :param divider: Value to divide dx,dy,dz by - useful to change units 194 :param relative: If true will change relative to last position, else will set head_position 195 ''' 196 if relative: 197 if dx is not None: head_position[:,0] += dx/divider 198 if dy is not None: head_position[:,1] += dy/divider 199 if dz is not None: head_position[:,2] += dz/divider 200 else: 201 if dx is not None: head_position[:,0] = dx/divider 202 if dy is not None: head_position[:,1] = dy/divider 203 if dz is not None: head_position[:,2] = dz/divider 204 205def extruder_to_point(points:list[Tensor], extruder:Tensor, max_stepsize:float=0.001, travel_type:str='hypot', via:Tensor|None=None ) -> list[Tensor]: 206 ''' 207 Will create a path from the extruder to each point in a shape \n 208 :param points: Points in shape 209 :param extruder: Extruder location 210 :param max_stepsize: Maximum stepsize allowed, default 1mm 211 :param travel_type: Type of movement to use, hypot: will use the shortest path, legsXY: will move in the XY plane then Z plane, legsZ: will move in the Z plane then XY plane, bezier, Will use a quadratic bezier in 3D 212 :param via: A point to move all paths through before going to the end point 213 :returns all points in path: 214 ''' 215 216 #No path planning -> Talk to Pengyuan? 217 218 all_points = [] 219 for p in points: 220 if via is not None: 221 all_points += start_to_end(extruder, via, max_stepsize, travel_type) #can be replaced by a 'function' call 222 all_points += start_to_end(via, p, max_stepsize, travel_type) 223 else: 224 all_points += start_to_end(extruder, p, max_stepsize, travel_type) 225 226 return all_points 227 228def start_to_end(end:Tensor, start:Tensor, max_stepsize:float=0.001, travel_type:str='hypot'): 229 ''' 230 Will create a path from the a start position to an end \n 231 :param points: start point location 232 :param end: end point location 233 :param max_stepsize: Maximum stepsize allowed, default 1mm 234 :returns all points in path: 235 ''' 236 237 points = [] 238 if travel_type == 'legsXY': #Move in XY plane then move in Z 239 240 mid_point = create_points(1,1,x=start[0].item(), y=start[1].item(), z=end[:,2].item()) 241 d = distance(start, mid_point) 242 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 243 points += interpolate_points(end, mid_point, N) 244 245 d = distance(mid_point, start) 246 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 247 points += interpolate_points(mid_point, start, N) 248 249 elif travel_type == 'legsZ': #Move in XY plane then move in Z 250 251 mid_point = create_points(1,1,x=end[:,0].item(), y=end[:,1].item(), z=start[2].item()) 252 253 254 d = distance(mid_point, start) 255 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 256 points += interpolate_points(end, mid_point, N) 257 258 d = distance(start, mid_point) 259 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 260 points += interpolate_points(mid_point, start, N) 261 262 elif travel_type == 'bezier': #Move along Bezier curve - paramatarised? 263 mid_point = create_points(1,1,x=start[0].item(), y=start[1].item(), z=end[:,2].item()) 264 offset_2 = mid_point - end 265 266 bezier = [end, start, [0,0,0], offset_2] 267 268 points += bezier_to_distance(bezier) 269 270 else: #default is hypot 271 d = distance(start, end) 272 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 273 points += interpolate_points(end, start, N) 274 275 return points 276 277 278 279def points_to_lcode_trap(points:list[Tensor], sig:str='Trap', function_name:str='') -> tuple[str,Tensor]: 280 ''' 281 Converts a set of points to a number of L1 commands (Traps) \n 282 :param points: The point locations 283 :param sig: The signature name to use 284 :param function_name: The name of the function these commands make up, if any 285 :returns command, head_position: The commands as string and the final head position 286 ''' 287 command = '' 288 if function_name != '': 289 command += f"function:{function_name}: \n" 290 for point in points: 291 N = point.shape[2] 292 sig_num = {'Focal':'0','Trap':"1",'Twin':'2','Vortex':'3'}[sig] 293 command += f"L{sig_num}:" 294 for i in range(N): 295 command += f'{point[:,0].item()},{point[:,1].item()},{point[:,2].item()}' 296 if i+1 < N: 297 command += ':' 298 command += ';\n' 299 300 head_position = point 301 302 if function_name != '': 303 command += f"end:{function_name}; \n" 304 305 return command, head_position 306 307def convert_G00(*args:str, head_position:Tensor, divider:float = 1000, relative:bool=False) -> tuple[str, Tensor]: 308 ''' 309 Comverts G00 commands to virtual head movements \n 310 :param args: Arguments to G00 command 311 :param head_position: strt position 312 :param divider: Value to divide dx,dy,dz by - useful to change units 313 :param relative: If true will change relative to last position, else will set head_position 314 :returns '', head_position: Returns an empty command and the new head position 315 ''' 316 dx, dy, dz = parse_xyz(*args) 317 318 update_head(head_position, dx, dy, dz, divider, relative) 319 320 return '', head_position 321 322def convert_G01(*args:str, head_position:Tensor, extruder:Tensor, divider:float = 1000, 323 relative:bool=False, max_stepsize:bool=0.001, pre_print_command:str = '', post_print_command:str = '', 324 sig:str='Trap', travel_type:str='hypot', add_optimisation_commands:bool=True, via:Tensor|None=None,functions:dict|None=None) -> tuple[str, Tensor]: 325 ''' 326 Comverts G00 commands to line of points \n 327 :param args: Arguments to G00 command 328 :param head_position: strt position 329 :param extruder: Extruder location 330 :param divider: Value to divide dx,dy,dz by - useful to change units 331 :param relative: If true will change relative to last position, else will set head_position 332 :param max_stepsize: Maximum stepsize allowed, default 1mm 333 :param pre_print_command: commands to put before generated commands 334 :param post_print_command: commands to put after generated commands 335 :param sig: Signature to use 336 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 337 :param via: A point to move all paths through before going to the end point 338 :returns command, head_position: Returns the commands and the new head position 339 340 ''' 341 dx, dy, dz = parse_xyz(*args) 342 343 end_position = head_position.clone() 344 345 update_head(end_position, dx, dy, dz, divider, relative) 346 347 N = int(torch.ceil(torch.max(distance(head_position, end_position) / max_stepsize)).item()) 348 if N > 0: 349 print_points = interpolate_points(head_position, end_position,N) 350 351 command, head_position = print_points_to_commands(print_points, extruder=extruder, max_stepsize=max_stepsize, 352 pre_print_command=pre_print_command, post_print_command=post_print_command, 353 sig=sig, travel_type=travel_type, add_optimisation_commands=add_optimisation_commands, via=via, 354 functions=functions) 355 else: 356 command = '' 357 return command, end_position, N 358 359def convert_G02_G03(*args, head_position:Tensor, extruder:Tensor, divider:float = 1000, 360 relative:bool=False, max_stepsize:float=0.001, anticlockwise:bool = False, 361 pre_print_command:str = '', post_print_command:str = '', sig:str='Trap', 362 travel_type:str='hypot', add_optimisation_commands:bool=True, via:Tensor|None=None, functions:dict|None=None)-> tuple[str, Tensor]: 363 ''' 364 Comverts G02 and G03 commands to arc of points \n 365 :param args: Arguments to G00 command 366 :param head_position: strt position 367 :param extruder: Extruder location 368 :param divider: Value to divide dx,dy,dz by - useful to change units 369 :param relative: If true will change relative to last position, else will set head_position 370 :param max_stepsize: Maximum stepsize allowed, default 1mm 371 :param anticlockwise: If true will arc anticlockwise, otherwise clockwise 372 :param pre_print_command: commands to put before generated commands 373 :param post_print_command: commands to put after generated commands 374 :param sig: Signature to use 375 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 376 :param via: A point to move all paths through before going to the end point 377 :returns command, head_position: Returns the commands and the new head position 378 ''' 379 380 dx, dy, dz = parse_xyz(*args) 381 382 end_position = head_position.clone() 383 origin = head_position.clone() 384 385 update_head(end_position, dx, dy, dz, divider, relative) 386 387 for arg in args: 388 arg = arg.lower() 389 if 'i' in arg: 390 I = float(arg.replace('i','')) 391 392 if 'j' in arg: 393 J = float(arg.replace('j','')) 394 395 update_head(origin, I, J, 0, divider, relative) 396 radius = distance(head_position, origin) 397 398 start_vec = (head_position-origin) 399 end_vec = (end_position-origin) 400 cos = torch.dot(start_vec.squeeze(),end_vec.squeeze()) / (torch.linalg.vector_norm(start_vec.squeeze()) * torch.linalg.vector_norm(end_vec.squeeze())) 401 402 angle = torch.acos(cos) 403 404 d = angle * radius 405 N = int(torch.ceil(torch.max( d / max_stepsize)).item()) 406 print_points = interpolate_arc(head_position, end_position, origin,n=N,anticlockwise=anticlockwise) 407 408 command, head_position = print_points_to_commands(print_points, extruder=extruder, max_stepsize=max_stepsize, 409 pre_print_command=pre_print_command, post_print_command=post_print_command, 410 sig=sig, travel_type=travel_type, add_optimisation_commands=add_optimisation_commands, via=via, 411 functions=functions) 412 413 return command, end_position, N 414 415def print_points_to_commands(print_points, extruder:Tensor, max_stepsize:float=0.001, 416 pre_print_command:str = '', post_print_command:str = '', sig:str='Trap', 417 travel_type:str='hypot', add_optimisation_commands:bool=True, via:Tensor|None=None, functions:dict|None={}): 418 ''' 419 @private 420 ''' 421 command = '' 422 for point in print_points: 423 if via is not None and functions is not None: 424 '''Check if we can use a function - if we have seen the same (extruder, travel_type, max_stepsize, via) before''' 425 extruder_x = extruder[:,0].item() 426 extruder_y = extruder[:,1].item() 427 extruder_z = extruder[:,2].item() 428 429 via_x = extruder[:,0].item() 430 via_y = extruder[:,1].item() 431 via_z = extruder[:,2].item() 432 433 idx = str(extruder_x) + '_' + str(extruder_y)+ '_' + str(extruder_z)+ '_' + travel_type+ '_' + \ 434 str(max_stepsize) + '_'+ str(via_x)+ '_' + str(via_y) + '_'+ str(via_z) 435 436 if idx not in functions: #This is the first time we've seen this so create it 437 438 if 'names' not in functions: 439 functions['names'] = 0 440 441 name = functions['names'] + 1 442 functions['names'] = name 443 444 pt = extruder_to_point(via, extruder, travel_type=travel_type, max_stepsize=max_stepsize) 445 cmd, head_position = points_to_lcode_trap(pt, sig=sig,function_name=f"F{name}") 446 command += pre_print_command 447 command += cmd 448 449 functions[idx] = (f"F{name};\n", head_position) 450 451 452 else: 453 '''Just use precomputed path''' 454 cmd, head_position = functions[idx] 455 command += pre_print_command 456 command += cmd 457 458 pt = extruder_to_point(point, via, travel_type=travel_type, max_stepsize=max_stepsize) 459 cmd, head_position = points_to_lcode_trap(pt, sig=sig) 460 command += cmd 461 command += post_print_command 462 if add_optimisation_commands: command += 'O0;\n' 463 464 465 else: 466 467 468 pt = extruder_to_point(point, extruder, travel_type=travel_type, max_stepsize=max_stepsize, via=via) 469 cmd, head_position = points_to_lcode_trap(pt, sig=sig) 470 command += pre_print_command 471 command += cmd 472 command += post_print_command 473 if add_optimisation_commands: command += 'O0;\n' 474 475 return command, head_position
def
gcode_to_lcode( fname: str, output_name: str | None = None, output_dir: str | None = None, log: bool = True, log_name: str | None = None, log_dir: str = None, divider: float = 1000, relative: bool = False, max_stepsize: float = 0.001, extruder: torch.Tensor | None = None, pre_print_command: str = '', post_print_command: str = '', print_lines: bool = False, pre_commands: str = '', post_commands: str = '', use_BEM: bool = False, sig_type: str = 'Trap', travel_type: Literal['hypot', 'legsXY', 'legsZ', 'bezier'] = 'hypot', add_optimisation_commands: bool = True, via: torch.Tensor | None = None, use_functions: bool = False):
17def gcode_to_lcode(fname:str, output_name:str|None=None, output_dir:str|None=None, log:bool=True, log_name:str|None=None, log_dir:str=None, 18 divider:float = 1000, relative:bool = False, 19 max_stepsize:float=0.001, extruder:Tensor|None = None, pre_print_command:str = '', 20 post_print_command:str = '', print_lines:bool=False, pre_commands:str= '', post_commands:str='', 21 use_BEM:bool = False, sig_type:str='Trap', travel_type:Literal["hypot","legsXY","legsZ","bezier"]='hypot', 22 add_optimisation_commands:bool=True, via:Tensor|None=None, use_functions:bool=False): 23 ''' 24 Converts a .gcode file to a .lcode file \n 25 ```Python 26 from acoustools.Fabrication.Translater import gcode_to_lcode 27 28 29 pth = 'acoustools/tests/data/gcode/rectangle.gcode' 30 31 pre_cmd = 'C0;\\n' 32 post_cmd = 'C1;\\nC3:10;\\nC2;\\n' 33 gcode_to_lcode(pth, pre_print_command=pre_cmd, post_print_command=post_cmd) 34 35 ``` 36 :param fname: The file name of the gcode file 37 :param output_name: The filename for the lcode file, if None will use the gcode file name with .gcode replaced with .lcode 38 :param output_dir: output directory of the lcode file, if None will use the same as the gcode file 39 :param log: If True will save log files 40 :param log_name: Name for the log file, if None will will use the gcode file name with .gcode replaced with .txt and the name with '_log' appended 41 :param log_dir: Directory for log file, if None will use same as the gcode file 42 :param divider: Value to divide dx,dy,dz by - useful to change units 43 :param relative: If true will change relative to last position, else will be absolute 44 :param max_stepsize: Maximum stepsize allowed, default 1mm 45 :param extruder: Extruder location, if None will use (0,0.10, 0) 46 :param pre_print_command: commands to put before the block resulting from each G01, G02 and G03 command 47 :param post_print_command: commands to put before the block resulting from each G01, G02 and G03 command 48 :param print_lines: If true will print which line is being processed 49 :param pre_commands: commands to put at the top of the file 50 :param post_commands: commands to put at the end of the file 51 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 52 :param via: A point to move all paths through before going to the end point 53 :param use_functions: If true will collapse common code into functions - needs `via` to be specified too 54 :param use_BEM: If True will use scattering for the mesh while it is built 55 :param sig_type: Defined signature to use for traps 56 :param travel_type: Way to move the particle 57 58 ''' 59 60 61 name = fname.replace('.gcode','') 62 parts = name.split('/') 63 name = parts[-1] 64 path = '/'.join(parts[:-1]) 65 66 if extruder is None: 67 extruder = create_points(1,1,0,-0.04, 0.04) 68 69 if output_name is None: 70 output_name = name 71 72 if output_dir is None: 73 output_dir = path 74 75 if log_dir is None: 76 log_dir = output_dir 77 78 if log_name is None: 79 log_name = name 80 81 if use_functions: 82 functions = {} 83 else: 84 functions = None 85 86 87 output_file = open(output_dir+'/'+output_name+'.lcode','w') 88 output_file.write(pre_commands) 89 90 if log: log_file = open(log_dir+'/'+log_name+'_log.txt','w') 91 92 head_position = create_points(1,1,0,0,0) 93 E_val = 0 94 95 96 97 with open(fname) as file: 98 lines = file.readlines() 99 Nl = len(lines) 100 for i,line in enumerate(lines): 101 if print_lines and i % 10 == 0: print(f'Computing, {i}/{Nl}', end='\r') 102 line = line.rstrip() 103 line_split = line.split() 104 if len(line_split) == 0 or line.startswith(';'): 105 if log: log_file.write(f'Line {i+1}, Ignoring line {line})\n') 106 continue 107 code = line_split[0] 108 args = line_split[1:] 109 command = '' 110 111 E_val = get_E_val(*args, E_val=E_val) 112 113 if (code == 'G00' or code == 'G0') or ((code == 'G01' or code == 'G1') and E_val == 0): #Non-fabricating move 114 head_z = head_position[:,2].item() 115 command, head_position = convert_G00(*args, head_position=head_position, divider=divider, relative=relative) 116 if use_BEM and head_z != head_position[:,2]: 117 command += 'C10;\n' 118 119 if log: log_file.write(f'Line {i+1}, G00 Command: Virtual head updated to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} ({line}), E value set to {E_val} \n') 120 elif code == 'G01' or code == 'G1': #Fabricating move 121 command, head_position, N = convert_G01(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 122 max_stepsize=max_stepsize,pre_print_command=pre_print_command, 123 post_print_command=post_print_command, sig=sig_type, travel_type=travel_type, via=via, 124 functions=functions) 125 126 if log: log_file.write(f'Line {i+1}, G01 Command: Line printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}), E value set to {E_val} \n') 127 128 elif code == 'G02' or code == 'G2': #Fabricating move 129 command, head_position, N = convert_G02_G03(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 130 max_stepsize=max_stepsize, anticlockwise=False,pre_print_command=pre_print_command, 131 post_print_command=post_print_command, sig=sig_type, 132 travel_type=travel_type, add_optimisation_commands=add_optimisation_commands , via=via, 133 functions=functions) 134 135 if log: log_file.write(f'Line {i+1}, G02 Command: Circle printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}) \n') 136 137 elif code == 'G03' or code == 'G3': #Fabricating arc 138 command, head_position, N = convert_G02_G03(*args, head_position=head_position, extruder=extruder, divider=divider, relative=relative, 139 max_stepsize=max_stepsize, anticlockwise=True,pre_print_command=pre_print_command, 140 post_print_command=post_print_command, sig=sig_type, 141 travel_type=travel_type , add_optimisation_commands=add_optimisation_commands , via=via, 142 functions=functions) 143 144 if log: log_file.write(f'Line {i+1}, G03 Command: Circle printed to {head_position[:,0].item()}, {head_position[:,1].item()}, {head_position[:,2].item()} in {N} steps ({line}) \n') 145 elif code == 'G04' or code == 'G4': #Dwell!! 146 pass 147 elif code.startswith(';'): 148 if log: log_file.write(f'Line {i+1}, Ignoring Comment ({line})\n') 149 150 else: #Ignore everything else 151 if log: log_file.write(f'Line {i+1}, Ignoring code {code} ({line})\n') 152 153 output_file.write(command) 154 155 156 output_file.write(post_commands) 157 output_file.close() 158 if print_lines: print() 159 if log: log_file.close()
Converts a .gcode file to a .lcode file
from acoustools.Fabrication.Translater import gcode_to_lcode
pth = 'acoustools/tests/data/gcode/rectangle.gcode'
pre_cmd = 'C0;\n'
post_cmd = 'C1;\nC3:10;\nC2;\n'
gcode_to_lcode(pth, pre_print_command=pre_cmd, post_print_command=post_cmd)
Parameters
- fname: The file name of the gcode file
- output_name: The filename for the lcode file, if None will use the gcode file name with .gcode replaced with .lcode
- output_dir: output directory of the lcode file, if None will use the same as the gcode file
- log: If True will save log files
- log_name: Name for the log file, if None will will use the gcode file name with .gcode replaced with .txt and the name with '_log' appended
- log_dir: Directory for log file, if None will use same as the gcode file
- divider: Value to divide dx,dy,dz by - useful to change units
- relative: If true will change relative to last position, else will be absolute
- max_stepsize: Maximum stepsize allowed, default 1mm
- extruder: Extruder location, if None will use (0,0.10, 0)
- pre_print_command: commands to put before the block resulting from each G01, G02 and G03 command
- post_print_command: commands to put before the block resulting from each G01, G02 and G03 command
- print_lines: If true will print which line is being processed
- pre_commands: commands to put at the top of the file
- post_commands: commands to put at the end of the file
- add_optimisation_commands: If True will add commands to help
acoustools.Fabrication.Optimsationfunctions work correctly - via: A point to move all paths through before going to the end point
- use_functions: If true will collapse common code into functions - needs
viato be specified too - use_BEM: If True will use scattering for the mesh while it is built
- sig_type: Defined signature to use for traps
- travel_type: Way to move the particle
def
get_E_val(*args: str, E_val: float):
def
parse_xyz(*args: str):
168def parse_xyz(*args:str): 169 ''' 170 Takes the args from a gcode line and ginds the XYZ arguments \n 171 :param args: list of arguments from gcode (all of the line but the command) 172 ''' 173 x,y,z = None,None,None #Need to check if any of these need to not be changed 174 for arg in args: 175 arg = arg.lower() 176 if 'x' in arg: 177 x = float(arg.replace('x','')) 178 179 if 'y' in arg: 180 y = float(arg.replace('y','')) 181 182 if 'z' in arg: 183 z = float(arg.replace('z','')) 184 185 return x,y,z
Takes the args from a gcode line and ginds the XYZ arguments
Parameters
- args: list of arguments from gcode (all of the line but the command)
def
update_head( head_position: torch.Tensor, dx: float, dy: float, dz: float, divider: float, relative: bool):
187def update_head(head_position: Tensor, dx:float, dy:float, dz:float, divider:float, relative:bool): 188 ''' 189 Updates the head (or other tensor) based on dx,dy,dz \n 190 :param head_position: Start position 191 :param dx: change (or new value) for x axis 192 :param dy: change (or new value) for y axis 193 :param dz: change (or new value) for z axis 194 :param divider: Value to divide dx,dy,dz by - useful to change units 195 :param relative: If true will change relative to last position, else will set head_position 196 ''' 197 if relative: 198 if dx is not None: head_position[:,0] += dx/divider 199 if dy is not None: head_position[:,1] += dy/divider 200 if dz is not None: head_position[:,2] += dz/divider 201 else: 202 if dx is not None: head_position[:,0] = dx/divider 203 if dy is not None: head_position[:,1] = dy/divider 204 if dz is not None: head_position[:,2] = dz/divider
Updates the head (or other tensor) based on dx,dy,dz
Parameters
- head_position: Start position
- dx: change (or new value) for x axis
- dy: change (or new value) for y axis
- dz: change (or new value) for z axis
- divider: Value to divide dx,dy,dz by - useful to change units
- relative: If true will change relative to last position, else will set head_position
def
extruder_to_point( points: list[torch.Tensor], extruder: torch.Tensor, max_stepsize: float = 0.001, travel_type: str = 'hypot', via: torch.Tensor | None = None) -> list[torch.Tensor]:
206def extruder_to_point(points:list[Tensor], extruder:Tensor, max_stepsize:float=0.001, travel_type:str='hypot', via:Tensor|None=None ) -> list[Tensor]: 207 ''' 208 Will create a path from the extruder to each point in a shape \n 209 :param points: Points in shape 210 :param extruder: Extruder location 211 :param max_stepsize: Maximum stepsize allowed, default 1mm 212 :param travel_type: Type of movement to use, hypot: will use the shortest path, legsXY: will move in the XY plane then Z plane, legsZ: will move in the Z plane then XY plane, bezier, Will use a quadratic bezier in 3D 213 :param via: A point to move all paths through before going to the end point 214 :returns all points in path: 215 ''' 216 217 #No path planning -> Talk to Pengyuan? 218 219 all_points = [] 220 for p in points: 221 if via is not None: 222 all_points += start_to_end(extruder, via, max_stepsize, travel_type) #can be replaced by a 'function' call 223 all_points += start_to_end(via, p, max_stepsize, travel_type) 224 else: 225 all_points += start_to_end(extruder, p, max_stepsize, travel_type) 226 227 return all_points
Will create a path from the extruder to each point in a shape
Parameters
- points: Points in shape
- extruder: Extruder location
- max_stepsize: Maximum stepsize allowed, default 1mm
- travel_type: Type of movement to use, hypot: will use the shortest path, legsXY: will move in the XY plane then Z plane, legsZ: will move in the Z plane then XY plane, bezier, Will use a quadratic bezier in 3D
- via: A point to move all paths through before going to the end point :returns all points in path:
def
start_to_end( end: torch.Tensor, start: torch.Tensor, max_stepsize: float = 0.001, travel_type: str = 'hypot'):
229def start_to_end(end:Tensor, start:Tensor, max_stepsize:float=0.001, travel_type:str='hypot'): 230 ''' 231 Will create a path from the a start position to an end \n 232 :param points: start point location 233 :param end: end point location 234 :param max_stepsize: Maximum stepsize allowed, default 1mm 235 :returns all points in path: 236 ''' 237 238 points = [] 239 if travel_type == 'legsXY': #Move in XY plane then move in Z 240 241 mid_point = create_points(1,1,x=start[0].item(), y=start[1].item(), z=end[:,2].item()) 242 d = distance(start, mid_point) 243 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 244 points += interpolate_points(end, mid_point, N) 245 246 d = distance(mid_point, start) 247 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 248 points += interpolate_points(mid_point, start, N) 249 250 elif travel_type == 'legsZ': #Move in XY plane then move in Z 251 252 mid_point = create_points(1,1,x=end[:,0].item(), y=end[:,1].item(), z=start[2].item()) 253 254 255 d = distance(mid_point, start) 256 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 257 points += interpolate_points(end, mid_point, N) 258 259 d = distance(start, mid_point) 260 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 261 points += interpolate_points(mid_point, start, N) 262 263 elif travel_type == 'bezier': #Move along Bezier curve - paramatarised? 264 mid_point = create_points(1,1,x=start[0].item(), y=start[1].item(), z=end[:,2].item()) 265 offset_2 = mid_point - end 266 267 bezier = [end, start, [0,0,0], offset_2] 268 269 points += bezier_to_distance(bezier) 270 271 else: #default is hypot 272 d = distance(start, end) 273 N = int(torch.ceil(torch.max(d / max_stepsize)).item()) 274 points += interpolate_points(end, start, N) 275 276 return points
Will create a path from the a start position to an end
Parameters
- points: start point location
- end: end point location
- max_stepsize: Maximum stepsize allowed, default 1mm :returns all points in path:
def
points_to_lcode_trap( points: list[torch.Tensor], sig: str = 'Trap', function_name: str = '') -> tuple[str, torch.Tensor]:
280def points_to_lcode_trap(points:list[Tensor], sig:str='Trap', function_name:str='') -> tuple[str,Tensor]: 281 ''' 282 Converts a set of points to a number of L1 commands (Traps) \n 283 :param points: The point locations 284 :param sig: The signature name to use 285 :param function_name: The name of the function these commands make up, if any 286 :returns command, head_position: The commands as string and the final head position 287 ''' 288 command = '' 289 if function_name != '': 290 command += f"function:{function_name}: \n" 291 for point in points: 292 N = point.shape[2] 293 sig_num = {'Focal':'0','Trap':"1",'Twin':'2','Vortex':'3'}[sig] 294 command += f"L{sig_num}:" 295 for i in range(N): 296 command += f'{point[:,0].item()},{point[:,1].item()},{point[:,2].item()}' 297 if i+1 < N: 298 command += ':' 299 command += ';\n' 300 301 head_position = point 302 303 if function_name != '': 304 command += f"end:{function_name}; \n" 305 306 return command, head_position
Converts a set of points to a number of L1 commands (Traps)
Parameters
- points: The point locations
- sig: The signature name to use
- function_name: The name of the function these commands make up, if any :returns command, head_position: The commands as string and the final head position
def
convert_G00( *args: str, head_position: torch.Tensor, divider: float = 1000, relative: bool = False) -> tuple[str, torch.Tensor]:
308def convert_G00(*args:str, head_position:Tensor, divider:float = 1000, relative:bool=False) -> tuple[str, Tensor]: 309 ''' 310 Comverts G00 commands to virtual head movements \n 311 :param args: Arguments to G00 command 312 :param head_position: strt position 313 :param divider: Value to divide dx,dy,dz by - useful to change units 314 :param relative: If true will change relative to last position, else will set head_position 315 :returns '', head_position: Returns an empty command and the new head position 316 ''' 317 dx, dy, dz = parse_xyz(*args) 318 319 update_head(head_position, dx, dy, dz, divider, relative) 320 321 return '', head_position
Comverts G00 commands to virtual head movements
Parameters
- args: Arguments to G00 command
- head_position: strt position
- divider: Value to divide dx,dy,dz by - useful to change units
- relative: If true will change relative to last position, else will set head_position :returns '', head_position: Returns an empty command and the new head position
def
convert_G01( *args: str, head_position: torch.Tensor, extruder: torch.Tensor, divider: float = 1000, relative: bool = False, max_stepsize: bool = 0.001, pre_print_command: str = '', post_print_command: str = '', sig: str = 'Trap', travel_type: str = 'hypot', add_optimisation_commands: bool = True, via: torch.Tensor | None = None, functions: dict | None = None) -> tuple[str, torch.Tensor]:
323def convert_G01(*args:str, head_position:Tensor, extruder:Tensor, divider:float = 1000, 324 relative:bool=False, max_stepsize:bool=0.001, pre_print_command:str = '', post_print_command:str = '', 325 sig:str='Trap', travel_type:str='hypot', add_optimisation_commands:bool=True, via:Tensor|None=None,functions:dict|None=None) -> tuple[str, Tensor]: 326 ''' 327 Comverts G00 commands to line of points \n 328 :param args: Arguments to G00 command 329 :param head_position: strt position 330 :param extruder: Extruder location 331 :param divider: Value to divide dx,dy,dz by - useful to change units 332 :param relative: If true will change relative to last position, else will set head_position 333 :param max_stepsize: Maximum stepsize allowed, default 1mm 334 :param pre_print_command: commands to put before generated commands 335 :param post_print_command: commands to put after generated commands 336 :param sig: Signature to use 337 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 338 :param via: A point to move all paths through before going to the end point 339 :returns command, head_position: Returns the commands and the new head position 340 341 ''' 342 dx, dy, dz = parse_xyz(*args) 343 344 end_position = head_position.clone() 345 346 update_head(end_position, dx, dy, dz, divider, relative) 347 348 N = int(torch.ceil(torch.max(distance(head_position, end_position) / max_stepsize)).item()) 349 if N > 0: 350 print_points = interpolate_points(head_position, end_position,N) 351 352 command, head_position = print_points_to_commands(print_points, extruder=extruder, max_stepsize=max_stepsize, 353 pre_print_command=pre_print_command, post_print_command=post_print_command, 354 sig=sig, travel_type=travel_type, add_optimisation_commands=add_optimisation_commands, via=via, 355 functions=functions) 356 else: 357 command = '' 358 return command, end_position, N
Comverts G00 commands to line of points
Parameters
- args: Arguments to G00 command
- head_position: strt position
- extruder: Extruder location
- divider: Value to divide dx,dy,dz by - useful to change units
- relative: If true will change relative to last position, else will set head_position
- max_stepsize: Maximum stepsize allowed, default 1mm
- pre_print_command: commands to put before generated commands
- post_print_command: commands to put after generated commands
- sig: Signature to use
- add_optimisation_commands: If True will add commands to help
acoustools.Fabrication.Optimsationfunctions work correctly - via: A point to move all paths through before going to the end point :returns command, head_position: Returns the commands and the new head position
def
convert_G02_G03( *args, head_position: torch.Tensor, extruder: torch.Tensor, divider: float = 1000, relative: bool = False, max_stepsize: float = 0.001, anticlockwise: bool = False, pre_print_command: str = '', post_print_command: str = '', sig: str = 'Trap', travel_type: str = 'hypot', add_optimisation_commands: bool = True, via: torch.Tensor | None = None, functions: dict | None = None) -> tuple[str, torch.Tensor]:
360def convert_G02_G03(*args, head_position:Tensor, extruder:Tensor, divider:float = 1000, 361 relative:bool=False, max_stepsize:float=0.001, anticlockwise:bool = False, 362 pre_print_command:str = '', post_print_command:str = '', sig:str='Trap', 363 travel_type:str='hypot', add_optimisation_commands:bool=True, via:Tensor|None=None, functions:dict|None=None)-> tuple[str, Tensor]: 364 ''' 365 Comverts G02 and G03 commands to arc of points \n 366 :param args: Arguments to G00 command 367 :param head_position: strt position 368 :param extruder: Extruder location 369 :param divider: Value to divide dx,dy,dz by - useful to change units 370 :param relative: If true will change relative to last position, else will set head_position 371 :param max_stepsize: Maximum stepsize allowed, default 1mm 372 :param anticlockwise: If true will arc anticlockwise, otherwise clockwise 373 :param pre_print_command: commands to put before generated commands 374 :param post_print_command: commands to put after generated commands 375 :param sig: Signature to use 376 :param add_optimisation_commands: If True will add commands to help `acoustools.Fabrication.Optimsation` functions work correctly 377 :param via: A point to move all paths through before going to the end point 378 :returns command, head_position: Returns the commands and the new head position 379 ''' 380 381 dx, dy, dz = parse_xyz(*args) 382 383 end_position = head_position.clone() 384 origin = head_position.clone() 385 386 update_head(end_position, dx, dy, dz, divider, relative) 387 388 for arg in args: 389 arg = arg.lower() 390 if 'i' in arg: 391 I = float(arg.replace('i','')) 392 393 if 'j' in arg: 394 J = float(arg.replace('j','')) 395 396 update_head(origin, I, J, 0, divider, relative) 397 radius = distance(head_position, origin) 398 399 start_vec = (head_position-origin) 400 end_vec = (end_position-origin) 401 cos = torch.dot(start_vec.squeeze(),end_vec.squeeze()) / (torch.linalg.vector_norm(start_vec.squeeze()) * torch.linalg.vector_norm(end_vec.squeeze())) 402 403 angle = torch.acos(cos) 404 405 d = angle * radius 406 N = int(torch.ceil(torch.max( d / max_stepsize)).item()) 407 print_points = interpolate_arc(head_position, end_position, origin,n=N,anticlockwise=anticlockwise) 408 409 command, head_position = print_points_to_commands(print_points, extruder=extruder, max_stepsize=max_stepsize, 410 pre_print_command=pre_print_command, post_print_command=post_print_command, 411 sig=sig, travel_type=travel_type, add_optimisation_commands=add_optimisation_commands, via=via, 412 functions=functions) 413 414 return command, end_position, N
Comverts G02 and G03 commands to arc of points
Parameters
- args: Arguments to G00 command
- head_position: strt position
- extruder: Extruder location
- divider: Value to divide dx,dy,dz by - useful to change units
- relative: If true will change relative to last position, else will set head_position
- max_stepsize: Maximum stepsize allowed, default 1mm
- anticlockwise: If true will arc anticlockwise, otherwise clockwise
- pre_print_command: commands to put before generated commands
- post_print_command: commands to put after generated commands
- sig: Signature to use
- add_optimisation_commands: If True will add commands to help
acoustools.Fabrication.Optimsationfunctions work correctly - via: A point to move all paths through before going to the end point :returns command, head_position: Returns the commands and the new head position