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.Optimsation functions 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 via to 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):
161def get_E_val(*args:str, E_val:float):
162    E = E_val
163    for arg in args:
164        if arg.startswith('E'):
165            E = float(arg[1:])
166    return E
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.Optimsation functions 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.Optimsation functions 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