Skip to content

Instantly share code, notes, and snippets.

@nmz787
Last active January 27, 2023 07:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nmz787/0b6bfd87a2ef21555f292de924001853 to your computer and use it in GitHub Desktop.
Save nmz787/0b6bfd87a2ef21555f292de924001853 to your computer and use it in GitHub Desktop.
converts a CSV to klayout, GDS3D, or Calibre layer-property files.
'''
usage:
open_source_layerprops_converter.py /path/to/num_to_names.csv /path/to/output/layerprops.ext -out_format klayout/gds3d/calibre
example CSV (not including this line):
1,0,metal0,drawing
1,2,metal0,via
2,0,metal1,drawing
'''
from typing import Union
colors1 = [ # from https://stackoverflow.com/a/20298027/253127
'#000000',
'#00FF00',
'#0000FF',
'#FF0000',
'#01FFFE',
'#FFA6FE',
'#FFDB66',
'#006401',
'#010067',
'#95003A',
'#007DB5',
'#FF00F6',
'#FFEEE8',
'#774D00',
'#90FB92',
'#0076FF',
'#D5FF00',
'#FF937E',
'#6A826C',
'#FF029D',
'#FE8900',
'#7A4782',
'#7E2DD2',
'#85A900',
'#FF0056',
'#A42400',
'#00AE7E',
'#683D3B',
'#BDC6FF',
'#263400',
'#BDD393',
'#00B917',
'#9E008E',
'#001544',
'#C28C9F',
'#FF74A3',
'#01D0FF',
'#004754',
'#E56FFE',
'#788231',
'#0E4CA1',
'#91D0CB',
'#BE9970',
'#968AE8',
'#BB8800',
'#43002C',
'#DEFF74',
'#00FFC6',
'#FFE502',
'#620E00',
'#008F9C',
'#98FF52',
'#7544B1',
'#B500FF',
'#00FF78',
'#FF6E41',
'#005F39',
'#6B6882',
'#5FAD4E',
'#A75740',
'#A5FFD2',
'#FFB167',
'#009BFF',
'#E85EBE'
]
colors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c',
'#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1',
'#000075', '#808080', '#ffffff', '#000000']
calibre_to_klayout_patterns = {'solid':'I0',
'clear': 'I1',
'diagonal_1': 'I4', # Diagonal /
'diagonal_2': 'I8', # Diagonal \
'wave': 'I14', # Crosses
'brick': 'I28', # square waves
'circles': 'I131',
'speckle': "I2" , # Dots dense
'light_speckle': "I3" # dots
}
layer_patterns = list(calibre_to_klayout_patterns.keys())
favorite_colors = { '1.0': ["#00ff00", "light_speckle", 0, 1],
'2.0': ["#00ff56", "wave", 0, 1],
'metal0_drawing': ["#ff0000", "speckle", 0, 1]
}
def convert(csv_path, outfile_path, out_format):
layerprops = []
layers_present= read_csv_number_to_name_table(csv_path)
print(f'\nCreating layer property file in format ({out_format}). Layers to convert:\n{layers_present}\n')
drawing_color_counter = 0
for i, nums in enumerate(sorted(layers_present, key=lambda x:f'{x[0]}_{x[1]}')):
layer, purpose = nums
lay_num, type_num = layers_present[nums]
if purpose == 'drawing':
color = colors[drawing_color_counter]
drawing_color_counter += 1
else:
color = '#123456'
if drawing_color_counter >= len(colors):
drawing_color_counter = 0
layer_display_pattern = layer_patterns[i%len(layer_patterns)] #'speckle'
default_on = 0
border_thickness = 1
if f'{lay_num}.{type_num}' in favorite_colors:
color, layer_display_pattern, default_on, border_thickness = favorite_colors[f'{lay_num}.{type_num}']
elif f'{layer}_{purpose}' in favorite_colors:
color, layer_display_pattern, default_on, border_thickness = favorite_colors[f'{layer}_{purpose}']
if out_format=='calibre':
# lay_num.type_num #123456 speckle layer_purpose 0 1
# layer# layertype# RGB# pattern stringName defaultShown borderThickness
layerprops.append(f'{lay_num}.{type_num} {color} {layer_display_pattern} {layer}_{purpose} {default_on} {border_thickness}')
elif out_format=='klayout':
layer_display_pattern = calibre_to_klayout_patterns[layer_display_pattern]
layerprops.append({
'source': f'{lay_num}/{type_num}@1', 'name': f'{layer}_{purpose}',
'frame-color': color, # black is #000000 but it looks sort of bad for all layers when zoomed out
'fill-color': color,
'frame-brightness': 0, 'fill-brightness': 0,
'dither-pattern': layer_display_pattern,
'line-style': '',
'valid': 'true',
'visible': 'true' if default_on else 'false',
'transparent': 'false',
'width': '',
'marked': 'false',
'xfill': 'false',
'animation': 0,
})
elif out_format == 'gds3d':
layerprops.append({
'layer_num': lay_num,
'layer_type': type_num,
'name': f'{layer}_{purpose}',
'color': (int(color[1:3], 16)/255, int(color[3:5], 16)/255, int(color[5:7], 16)/255)
})
if out_format=='klayout':
layerprops = object_to_xml(layerprops, 'layer-properties')
elif out_format == 'calibre':
layerprops = '\n'.join(layerprops)
elif out_format=='gds3d':
layer_dict = {d['name']:d for d in layerprops}
layers_prelim_sorted = sorted([d['name'] for d in layerprops])[::-1]
temp_path = outfile_path+'.tempsorting'
with open(temp_path, 'w') as f:
f.write('\n'.join(layers_prelim_sorted))
input(f'OPEN file in text editor and sort stack (top is upper metals, bottom is bulk substrate), then save and close editor:\n{temp_path}\n\npress enter when done')
with open(temp_path, 'r') as f:
sorted_human = f.readlines()
# import pdb;pdb.set_trace()
sorted_layer_names = [l.strip() for l in sorted_human]
layer_start_thick = {}
start_z = 0
thick = 0
for layer in reversed(sorted_layer_names):
print(f'\n\n layer, startz, thickness')
print('\n'.join([f'{k},{v}' for k,v in layer_start_thick.items()]))
newstart = start_z + thick
start_z = int(input(f'enter starting Z (nm) for layer {layer} - (default: {newstart}):') or newstart)
thick = int(input(f'enter thickness for layer {layer} - (default: {thick}):') or thick)
layer_start_thick[layer]=(start_z, thick)
out_str = """LayerStart: Substrate
Layer: 255
Datatype: 0
Height: -10000.0
Thickness: 10000.0
Red: 0.15
Green: 0.15
Blue: 0.15
Filter: 0.0
Metal: 0
Show: 1
LayerEnd
"""
for layer in sorted_layer_names:
layer_data = layer_dict[layer]
r,g,b = layer_data['color']
num = layer_data['layer_num']
dtype = layer_data['layer_type']
start, thick = layer_start_thick[layer]
out_str += f'''\n\nLayerStart: {layer}\nLayer: {num}
Datatype: {dtype}
Height: {start}
Thickness: {thick}
Red: {r}
Green: {g}
Blue: {b}
Show: 1
LayerEnd
'''
layerprops = out_str
with open(outfile_path, 'w') as f:
f.write(layerprops)
newline = '\n'
tab = '\t'
def object_to_xml(data: Union[dict, bool], root='object', indent=0, compressed=False):
xml = f'{indent*tab}<{root}>{newline if (not compressed and isinstance(data, (list, tuple, set, dict))) else ""}'
if isinstance(data, dict):
for key, value in data.items():
xml += object_to_xml(value, key, indent=indent+1)
elif isinstance(data, (list, tuple, set)):
for item in data:
xml += object_to_xml(item, 'properties', indent=indent+1)
else:
xml += str(data)
xml += f'{(indent)*tab if xml[-1]==newline else ""}</{root}>{newline if not compressed else ""}'
return xml
def read_csv_number_to_name_table(csv_path):
num2name = {}
with open (csv_path) as f:
for line in f:
layer_num, type_num, layer_name, type_name = line.split(',')
num2name[(layer_num, type_num)] = (layer_name, type_name)
return num2name
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser("open_source_layerprops_converter.py /path/to/num_to_names.csv /path/to/output/layerprops")
parser.add_argument('csv_path', type=str, help="path to the lookup which has lines in format \"1,2,metal0,drawing\"")
parser.add_argument('out_path', type=str, help="path to save the output")
parser.add_argument('-out_format', type=str, help="options are: klayout, gds3d, calibre", required = True)
args = parser.parse_args()
convert(args.csv_path, args.out_path, args.out_format)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment