vcd-render/8seg_vcd_render.py
2021-12-06 18:50:08 +01:00

146 lines
4.6 KiB
Python

import os
from os import path
from uuid import uuid4
import copy
from os import path
import sys
from bs4 import BeautifulSoup
import vcdvcd
from flask import Flask, redirect, url_for, abort, send_file, render_template, request, flash, Response
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1000
app.config.from_envvar('VCD8SEG_SETTINGS')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/show/<uuid:uuid>')
def show(uuid):
uuid = str(uuid)
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
if not path.isfile(fp):
abort(404)
return send_file(fp, mimetype='image/svg+xml')
@app.route('/upload', methods=['POST'])
def upload_vcd():
if not (file := request.files.get('file')):
flash('Request is missing file part.')
return redirect(url_for('index'))
if file.filename == '':
flash('No file selected.')
return redirect(url_for('index'))
if path.splitext(file.filename)[1].lower() != '.vcd':
flash('Please upload a .vcd waveform file (not e.g. .v or .vvp)')
return redirect(url_for('index'))
uuid = str(uuid4())
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
file.save(fp)
return redirect(url_for('vcd_setup', uuid=uuid))
@app.route('/setup/<uuid:uuid>', methods=['GET', 'POST'])
def vcd_setup(uuid):
uuid = str(uuid)
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
if not path.isfile(fp):
abort(404)
with open(fp) as f:
vcd = f.read()
if request.method == 'GET':
default_signal, signal_names = vcd_signal_names(vcd)
return render_template('setup.html', uuid=uuid, signal_names=signal_names, default_signal=default_signal)
else: # request.method == 'POST'
try:
svg = vcd_to_8seg_svg(vcd, request.form['signal'])
except IndexError:
abort(Response(400, 'Signal not found'))
os.remove(path.join(app.config['UPLOAD_PATH'], secure_filename(uuid)))
uuid = str(uuid4())
fp = path.join(app.config['UPLOAD_PATH'], secure_filename(uuid))
with open(fp, 'w') as f:
f.write(svg)
return redirect(url_for('show', uuid=uuid))
def vcd_signal_names(data):
vcd = vcdvcd.VCDVCD(vcd_string=data)
signal_names = list(vcd.references_to_ids.keys())
default_signal = [*[n for n in signal_names if 'segments' in n],
*[n for n in signal_names if 'segs' in n],
*signal_names][0]
return default_signal, signal_names
def vcd_to_8seg_svg(in_vcd, signal=None):
with open(path.join(app.static_folder, '8seg-template.svg')) as f:
soup = BeautifulSoup(f.read(), 'xml')
(vb_x, vb_y, vb_w, vb_h) = list(map(int, soup.find('svg')['viewBox'].split()))
display_template = soup.find(id='display').extract()
def generate_digit(segments):
inst = copy.copy(display_template)
for seg, val in zip('abcdefgh', segments):
if val:
tag = inst.find(id=f'seg_{seg}')
tag['style'] = tag['style'].replace('fill:#202040', 'fill:#ff0000')
return inst
def concat_digits(digits):
inst = copy.copy(soup)
svg = inst.find('svg')
x, y = vb_x, vb_y
w, h = 0, 0
for i, segments in enumerate(digits):
digit = generate_digit(segments)
digit['id'] = f'digit{i}'
digit['transform'] = f'translate({x}, {y})'
svg.append(digit)
x += vb_w
w += vb_w
h = vb_h
w_phys = (i+1) * 15 # mm
svg['viewBox'] = f'{vb_x} {vb_y} {w} {h}'
svg['width'] = f'{w_phys}mm'
svg['height'] = f'{15 * (vb_h / vb_w)}mm'
return svg
def parse_vcd(data, signal=None):
vcd = vcdvcd.VCDVCD(vcd_string=data) # vcd? vcd vcd, vcd!
signal_names = vcd.references_to_ids.keys()
if signal is None:
signal = [*[n for n in signal_names if 'segments' in n],
*[n for n in signal_names if 'segs' in n],
*signal_names][0]
try:
sig = vcd[signal]
except IndexError:
raise IndexError('Signal {signal} not found. Available signals: {", ".join(signal_names)}')
# Only use first 10k rows
for i, (time, value) in zip(range(10000), sig.tv):
value = ('0' * int(sig.size) + value)[-int(sig.size):]
yield [int(x) for x in value]
return str(concat_digits(list(parse_vcd(in_vcd, signal))))