mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
peftGDS=>PNG render improvement for GitHub Actions and local (#28)
* Add --create-png-preview option; more-robust GDS to PNG rendering for GHA * Minor update to --create-png-preview * Change --create-png-preview to just be a --create-png replacement * Minor --create-png improvement --------- Co-authored-by: Anton Maurovic <algo@foogle.com>
This commit is contained in:
co-authored by
Anton Maurovic
parent
e981222768
commit
76835903c0
+101
-5
@@ -490,17 +490,113 @@ class Project():
|
|||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
logging.error("pdf command failed")
|
logging.error("pdf command failed")
|
||||||
|
|
||||||
# SVG and PNG renders of the GDS
|
# Read and return top-level GDS data from the final GDS file, using gdstk:
|
||||||
def create_svg(self):
|
def get_final_gds_top_cells(self):
|
||||||
gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/gds/*gds'))
|
gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/gds/*gds'))
|
||||||
if self.args.openlane2:
|
if self.args.openlane2:
|
||||||
gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/final/gds/*gds'))
|
gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/final/gds/*gds'))
|
||||||
library = gdstk.read_gds(gds[0])
|
library = gdstk.read_gds(gds[0])
|
||||||
top_cells = library.top_level()
|
top_cells = library.top_level()
|
||||||
top_cells[0].write_svg('gds_render.svg')
|
return top_cells[0]
|
||||||
|
|
||||||
|
# SVG render of the GDS.
|
||||||
|
# NOTE: This includes all standard GDS layers inc. text labels.
|
||||||
|
def create_svg(self):
|
||||||
|
top_cells = self.get_final_gds_top_cells()
|
||||||
|
top_cells.write_svg('gds_render.svg')
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
sky130_label_layers = [
|
||||||
|
(64,59), # 64/59 - pwell.label
|
||||||
|
(64,5), # 64/5 - nwell.label
|
||||||
|
(67,5), # 67/5 - li1.label
|
||||||
|
(68,5), # 68/5 - met1.label
|
||||||
|
(69,5), # 69/5 - met2.label
|
||||||
|
(70,5), # 70/5 - met3.label
|
||||||
|
(71,5), # 71/5 - met4.label
|
||||||
|
]
|
||||||
|
top_cells.filter(sky130_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:
|
||||||
|
sky130_buried_layers = [
|
||||||
|
(64,16), # 64/16 - nwell.pin
|
||||||
|
(65,44), # 65/44 - tap.drawing
|
||||||
|
(68,16), # 68/16 - met1.pin
|
||||||
|
(68,44), # 68/44 - via.drawing
|
||||||
|
(81,4 ), # 81/4 - areaid.standardc
|
||||||
|
(70,20), # 70/20 - met3.drawing
|
||||||
|
# Important:
|
||||||
|
# (68,20), # 68/20 - met1.drawing
|
||||||
|
# Less important, but keep for now:
|
||||||
|
# (69,20), # 69/20 - met2.drawing
|
||||||
|
]
|
||||||
|
top_cells.filter(sky130_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.yaml['project']['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)))
|
||||||
|
|
||||||
if self.args.create_png:
|
|
||||||
cairosvg.svg2png(url='gds_render.svg', write_to='gds_render.png')
|
|
||||||
|
|
||||||
def print_warnings(self):
|
def print_warnings(self):
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|||||||
+4
-1
@@ -71,5 +71,8 @@ if __name__ == '__main__':
|
|||||||
if args.create_pdf:
|
if args.create_pdf:
|
||||||
project.create_pdf()
|
project.create_pdf()
|
||||||
|
|
||||||
if args.create_svg or args.create_png:
|
if args.create_png:
|
||||||
|
project.create_png()
|
||||||
|
|
||||||
|
if args.create_svg:
|
||||||
project.create_svg()
|
project.create_svg()
|
||||||
|
|||||||
Reference in New Issue
Block a user