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/') 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/', 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))))