Last active
January 27, 2023 07:47
-
-
Save nmz787/0b6bfd87a2ef21555f292de924001853 to your computer and use it in GitHub Desktop.
converts a CSV to klayout, GDS3D, or Calibre layer-property files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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