Initial commit
This commit is contained in:
commit
94633e2bf8
7 changed files with 337 additions and 0 deletions
146
8seg_vcd_render.py
Normal file
146
8seg_vcd_render.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
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))))
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue