mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(tt_tool): scramble stdcell layers in png render for IHP & GF (#129)
* feat(tt_tool): scramble stdcell layers in png render for IHP & GF * fix(tt_tool): resolve mypy errors * refactor(tt_tool): separate svg & png rendering into render_utils.py
This commit is contained in:
+13
-107
@@ -9,9 +9,7 @@ import shutil
|
||||
import subprocess
|
||||
import typing
|
||||
|
||||
import cairosvg # type: ignore
|
||||
import chevron
|
||||
import gdstk # type: ignore
|
||||
import klayout.db as pya
|
||||
import yaml
|
||||
from git.repo import Repo
|
||||
@@ -21,6 +19,7 @@ from config_utils import read_config, write_config
|
||||
from doc_utils import DocsHelper
|
||||
from markdown_utils import limit_markdown_headings
|
||||
from project_info import ProjectInfo, ProjectYamlError
|
||||
from render_utils import render_png, render_svg
|
||||
from tech import TechName, tech_map
|
||||
|
||||
PINOUT_KEYS = [
|
||||
@@ -300,7 +299,7 @@ class Project:
|
||||
repo = Repo(os.path.join(self.local_dir, "tt"))
|
||||
if repo.head.is_detached:
|
||||
ref = next(
|
||||
(ref.name for ref in repo.refs if ref.commit == repo.head.commit),
|
||||
(ref.name for ref in repo.refs if ref.commit == repo.head.commit), # type: ignore[attr-defined] # see mypy issue #8085
|
||||
"(detached)",
|
||||
)
|
||||
else:
|
||||
@@ -742,122 +741,29 @@ class Project:
|
||||
|
||||
DocsHelper.compile("./docs/doc.typ")
|
||||
|
||||
# Read and return top-level GDS data from the final GDS file, using gdstk:
|
||||
def get_final_gds_top_cells(self):
|
||||
# Return final GDS used for create_svg and create_png
|
||||
def get_final_gds(self):
|
||||
if "GDS_PATH" in os.environ:
|
||||
gds_path = os.environ["GDS_PATH"]
|
||||
return os.environ["GDS_PATH"]
|
||||
else:
|
||||
gds_path = glob.glob(
|
||||
return glob.glob(
|
||||
os.path.join(self.local_dir, "runs/wokwi/final/gds/*.gds")
|
||||
)[0]
|
||||
library = gdstk.read_gds(gds_path)
|
||||
top_cells = library.top_level()
|
||||
return top_cells[0]
|
||||
|
||||
# SVG render of the GDS.
|
||||
# NOTE: This includes all standard GDS layers inc. text labels.
|
||||
# SVG render of the GDS
|
||||
def create_svg(self):
|
||||
top_cells = self.get_final_gds_top_cells()
|
||||
top_cells.write_svg("gds_render.svg")
|
||||
render_svg(self.get_final_gds(), scramble_cells=self.tech.scramble_cells)
|
||||
|
||||
# Try various QUICK methods to create a more-compressed PNG render of the GDS,
|
||||
# and fall back to cairosvg if it doesn't work. This is designed for speed,
|
||||
# and in particular for use by the GitHub Actions.
|
||||
# For more info, see:
|
||||
# https://github.com/TinyTapeout/tt-gds-action/issues/8
|
||||
def create_png(self):
|
||||
logging.info("Loading GDS data...")
|
||||
top_cells = self.get_final_gds_top_cells()
|
||||
|
||||
# Remove label layers (i.e. delete text), then generate SVG:
|
||||
label_layers = self.tech.label_layers
|
||||
top_cells.filter(label_layers)
|
||||
for subcell in top_cells.dependencies(True):
|
||||
subcell.filter(label_layers)
|
||||
svg = "gds_render_preview.svg"
|
||||
logging.info("Rendering SVG without text label layers: {}".format(svg))
|
||||
top_cells.write_svg(svg, pad=0)
|
||||
# We should now have gds_render_preview.svg
|
||||
|
||||
# Try converting using 'rsvg-convert' command-line utility.
|
||||
# This should create gds_render_preview.png
|
||||
png = "gds_render_preview.png"
|
||||
logging.info("Converting to PNG using rsvg-convert: {}".format(png))
|
||||
|
||||
cmd = "rsvg-convert --unlimited {} -o {} --no-keep-image-data".format(svg, png)
|
||||
logging.debug(cmd)
|
||||
p = subprocess.run(cmd, shell=True, capture_output=True)
|
||||
|
||||
if p.returncode == 127:
|
||||
logging.warning(
|
||||
'rsvg-convert not found; is package "librsvg2-bin" installed? Falling back to cairosvg. This might take a while...'
|
||||
)
|
||||
# Fall back to cairosvg:
|
||||
cairosvg.svg2png(url=svg, write_to=png)
|
||||
|
||||
elif p.returncode != 0 and b"cannot load more than" in p.stderr:
|
||||
logging.warning(
|
||||
'Too many SVG elements ("{}"). Reducing complexity...'.format(
|
||||
p.stderr.decode().strip()
|
||||
)
|
||||
)
|
||||
|
||||
# Remove more layers that are hardly visible anyway:
|
||||
buried_layers = self.tech.buried_layers
|
||||
top_cells.filter(buried_layers)
|
||||
for subcell in top_cells.dependencies(True):
|
||||
subcell.filter(buried_layers)
|
||||
svg_alt = "gds_render_preview_alt.svg"
|
||||
logging.info("Rendering SVG with more layers removed: {}".format(svg_alt))
|
||||
top_cells.write_svg(svg_alt, pad=0)
|
||||
logging.info("Converting to PNG using rsvg-convert: {}".format(png))
|
||||
|
||||
cmd = "rsvg-convert --unlimited {} -o {} --no-keep-image-data".format(
|
||||
svg_alt, png
|
||||
)
|
||||
logging.debug(cmd)
|
||||
p = subprocess.run(cmd, shell=True, capture_output=True)
|
||||
|
||||
if p.returncode != 0:
|
||||
logging.warning(
|
||||
'Still cannot convert to SVG ("{}"). Falling back to cairosvg. This might take a while...'.format(
|
||||
p.stderr.decode().strip()
|
||||
)
|
||||
)
|
||||
|
||||
# Fall back to cairosvg, and since we're doing that, might as well use the original full-detail SVG anyway:
|
||||
cairosvg.svg2png(url=svg, write_to=png)
|
||||
|
||||
# By now we should have gds_render_preview.png
|
||||
|
||||
# Compress with pngquant:
|
||||
final_png = "gds_render.png"
|
||||
if self.info.tiles == "8x2":
|
||||
quality = "0-10" # Compress more for 8x2 tiles.
|
||||
else:
|
||||
quality = "0-30"
|
||||
logging.info("Compressing PNG further with pngquant to: {}".format(final_png))
|
||||
|
||||
cmd = "pngquant --quality {} --speed 1 --nofs --strip --force --output {} {}".format(
|
||||
quality, final_png, png
|
||||
)
|
||||
logging.debug(cmd)
|
||||
p = subprocess.run(cmd, shell=True, capture_output=True)
|
||||
|
||||
if p.returncode == 127:
|
||||
logging.warning(
|
||||
'pngquant not found; is package "pngquant" installed? Using intermediate (uncompressed) PNG file'
|
||||
)
|
||||
os.rename(png, final_png)
|
||||
elif p.returncode != 0:
|
||||
logging.warning(
|
||||
'pngquant error {} ("{}"). Using intermediate (uncompressed) PNG file'.format(
|
||||
p.returncode, p.stderr.decode().strip()
|
||||
)
|
||||
)
|
||||
os.rename(png, final_png)
|
||||
logging.info(
|
||||
"Final PNG is {} ({:,} bytes)".format(final_png, os.path.getsize(final_png))
|
||||
render_png(
|
||||
self.get_final_gds(),
|
||||
scramble_cells=self.tech.scramble_cells,
|
||||
buried_layers=self.tech.buried_layers,
|
||||
quality=quality,
|
||||
)
|
||||
|
||||
def print_warnings(self):
|
||||
|
||||
Reference in New Issue
Block a user