| from PIL import Image, ImageDraw, ImageFont, ImageOps |
| |
| import os |
| import sys |
| import json |
| |
| from JSONWrapper import JSONWrapper |
| |
| def ParseGuideFile(fileName:str) -> dict[str, list[tuple[float, float, float, float]]]: |
| if not os.path.exists(fileName) or os.path.splitext(fileName)[1] != ".guide": |
| return {} |
| |
| with open(fileName, "r") as f: |
| lines = f.readlines() |
| |
| data = { "other": [] } |
| |
| for l in lines: |
| parts = l.strip().split(' ') |
| if len(parts) == 5: |
| if parts[4] not in [parts[4]]: |
| print(f"Found unknown layer type '{parts[4]}'") |
| parts[4] = "other" |
| if parts[4] not in data: |
| data[parts[4]] = [] |
| data[parts[4]].append((float(parts[0]) * 0.001, float(parts[1]) * 0.001, float(parts[2]) * 0.001, float(parts[3]) * 0.001)) |
| |
| return data |
| |
| def LoadDRCErrorLocations(fileName:str) -> list[tuple[float, float, float, float]]: |
| if not os.path.exists(fileName): |
| return [] |
| |
| with open(fileName, "r") as f: |
| lines = f.readlines() |
| |
| data = [] |
| |
| for l in lines: |
| parts = l.strip().split(' ') |
| if len(parts) == 4: |
| data.append((parts[0], parts[1], parts[2], parts[3])) |
| |
| print(f"Loaded {len(data)} DRC errors") |
| |
| return data |
| |
| def LoadMacroPlacementFile(macroPlacementFileName:str) -> JSONWrapper: |
| if not os.path.exists(macroPlacementFileName): |
| print(f"Can't find macro placement file '{macroPlacementFileName}'") |
| return JSONWrapper(macroPlacementFileName, ".", ".", {}) |
| |
| if os.path.splitext(macroPlacementFileName)[1] != ".json": |
| print(f"Can't find file '{macroPlacementFileName}' as it isn't a json file") |
| return JSONWrapper(macroPlacementFileName, ".", ".", {}) |
| |
| with open(macroPlacementFileName, 'r') as f: |
| data = json.load(f) |
| |
| return JSONWrapper(macroPlacementFileName, ".", ".", data) |
| |
| def GetMacroBounds(positionX:float, positionY:float, width:float, height:float, rotation:str) -> tuple[float, float, float, float]: |
| if rotation == "N" or rotation == "S" or rotation == "FN" or rotation == "FS": |
| return (positionX, positionY, positionX + width, positionY + height) |
| elif rotation == "E" or rotation == "W" or rotation == "FE" or rotation == "FW": |
| return (positionX, positionY, positionX + height, positionY + width) |
| else: |
| print(f"Invalid rotation '{rotation}'") |
| return (positionX, positionY, positionX + width, positionY + height) |
| |
| def DrawText(image, bounds:tuple[float, float, float, float], text:str): |
| width = abs(bounds[2] - bounds[0]) |
| height = abs(bounds[3] - bounds[1]) |
| |
| fontSize = 200 |
| textWidth = width + 1 |
| while textWidth > width: |
| fnt = ImageFont.truetype("arial.ttf", fontSize) |
| textWidth = fnt.getlength(text) |
| fontSize -= 10 |
| |
| rotated = False |
| if fontSize < 100 and height > width: |
| fontSizeAlt = 200 |
| textWidth = height + 1 |
| while textWidth > height: |
| fnt = ImageFont.truetype("arial.ttf", fontSizeAlt) |
| textWidth = fnt.getlength(text) |
| fontSizeAlt -= 10 |
| if fontSizeAlt > fontSize: |
| fontSize = fontSizeAlt |
| rotated = True |
| |
| fnt = ImageFont.truetype("arial.ttf", fontSize) |
| textSize = fnt.getsize(text) |
| textIm=Image.new('L', textSize) |
| textDraw = ImageDraw.Draw(textIm) |
| textDraw.text((0.5 * textSize[0], 0.5 * textSize[1]), text, font=fnt, anchor="mm", align="center", fill=255) |
| |
| if rotated: |
| w = textIm.rotate(90, expand=True) |
| else: |
| w = textIm |
| |
| px = bounds[0] + (0.5 * (width - w.width)) |
| py = bounds[3] + (0.5 * (height - w.height)) |
| |
| image.paste(ImageOps.colorize(w, "black", "white"), (int(px), int(py)), w) |
| |
| def ViewGuideFile(guideFileName:str, macroPlacementFileName:str): |
| if not os.path.exists(guideFileName): |
| print(f"Can't find guide file '{guideFileName}' to render") |
| return |
| |
| if os.path.splitext(guideFileName)[1] != ".guide": |
| print(f"Can't find file '{guideFileName}' as it isn't a guide file") |
| return |
| |
| data = ParseGuideFile(guideFileName) |
| |
| chipWidth = 2908.58 |
| chipHeight = 3497.92 |
| boarder = 100 |
| imageScale = 2 |
| |
| imageWidth = int((chipWidth + boarder + boarder) * imageScale) + 1 |
| imageHeight = int((chipHeight + boarder + boarder) * imageScale) + 1 |
| |
| imBase = Image.new(mode="RGBA", size=(imageWidth, imageHeight), color=(15, 15, 15, 255)) |
| draw = ImageDraw.Draw(imBase, "RGBA") |
| |
| textToDraw = [] |
| |
| if macroPlacementFileName != "" and os.path.exists(macroPlacementFileName): |
| root = LoadMacroPlacementFile(macroPlacementFileName) |
| macroPlacement = root.GetEntry("macros") |
| for item in macroPlacement: |
| position = item.GetEntry("position") |
| size = item.GetEntry("size") |
| rotation = item.GetEntry("rotation") |
| bounds = GetMacroBounds( |
| position.GetEntry("x").AsFloat(), |
| position.GetEntry("y").AsFloat(), |
| size.GetEntry("x").AsFloat(), |
| size.GetEntry("y").AsFloat(), |
| rotation.AsString()) |
| px0 = (bounds[0] + boarder) * imageScale |
| py0 = (chipHeight - bounds[1] + boarder) * imageScale |
| px1 = (bounds[2] + boarder) * imageScale |
| py1 = (chipHeight - bounds[3] + boarder) * imageScale |
| draw.rectangle((px0, py0, px1, py1), fill=(50, 50, 50, 255), outline=(150, 150, 150, 255), width=5) |
| |
| if item.HasEntry("displayName"): |
| textToDraw.append(((px0, py0, px1, py1), item.GetEntry("displayName").AsString())) |
| |
| def drawRect(d, r, c): |
| px0 = int((r[0] + boarder) * imageScale) |
| py0 = int((chipHeight - r[1] + boarder) * imageScale) |
| px1 = int((r[2] + boarder) * imageScale) |
| py1 = int((chipHeight - r[3] + boarder) * imageScale) |
| d.rectangle((px0, py0, px1, py1), fill=c, outline=None, width=0) |
| |
| def drawLayer(name, c): |
| layerIm = imBase.copy()# Image.new(mode="RGBA", size=(imageWidth, imageHeight), color=(15, 15, 15, 255)) |
| layerDraw = ImageDraw.Draw(layerIm, "RGBA") |
| |
| if name in data: |
| for r in data[name]: |
| drawRect(layerDraw, r, (c[0], c[1], c[2], 255)) |
| |
| return Image.blend(imBase, layerIm, c[3] / 255.0) |
| |
| imBase = drawLayer("li1" , (100, 100, 100, 200)) |
| imBase = drawLayer("met1" , ( 0, 121, 198, 200)) |
| imBase = drawLayer("met2" , (164, 0, 164, 200)) |
| imBase = drawLayer("met3" , ( 30, 159, 0, 200)) |
| imBase = drawLayer("met4" , (205, 19, 5, 200)) |
| imBase = drawLayer("met5" , (239, 239, 0, 200)) |
| imBase = drawLayer("other", (255, 255, 255, 200)) |
| |
| for item in textToDraw: |
| DrawText(imBase, item[0], item[1]) |
| |
| imDraw = ImageDraw.Draw(imBase, "RGBA") |
| errorData = LoadDRCErrorLocations("ExperiarSoC/precheck_results/24_MAY_2022___21_15_27/outputs/reports/magic_drc_check.drc.report") |
| for item in errorData: |
| drawRect(imDraw, (item[0]-20, item[1]-20, item[2]+20, item[3]+20), (255, 0, 0, 255)) |
| |
| # write to stdout |
| imBase.save(f"{guideFileName}.jpg", "PNG") |
| print(f"Saved output image: '{guideFileName}.jpg'") |
| |
| def main(): |
| if len(sys.argv) > 1: |
| macroPlacementFile = "" |
| for i in range(1, len(sys.argv)): |
| item = sys.argv[i] |
| if type(item) is str: |
| if os.path.splitext(item)[1] == ".json": |
| if macroPlacementFile == "": |
| macroPlacementFile = item |
| else: |
| print("Multiple macro placement files specified") |
| |
| for i in range(1, len(sys.argv)): |
| item = sys.argv[i] |
| if type(item) is str: |
| if os.path.splitext(item)[1] != ".json": |
| ViewGuideFile(str(item), macroPlacementFile) |
| else: |
| print(f"Invalid argument '{item}' but be a path to a guide file") |
| else: |
| ViewGuideFile("docs/Scripts/detailed.guide", "openlane/user_project_wrapper/macros.json") |
| |
| if __name__ == "__main__": |
| main() |