Initial commit

This commit is contained in:
jaseg 2021-12-06 18:50:08 +01:00
commit 94633e2bf8
7 changed files with 337 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__

146
8seg_vcd_render.py Normal file
View 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))))

57
static/8seg-template.svg Normal file
View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg5"
sodipodi:docname="8seg-template.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
style="background-color: black;">
<g id="display">
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.323819px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 37.088054,29.110165 47.664515,21.178664 H 169.29381 l 10.57646,7.931501 -10.57646,7.931502 H 47.664515 Z"
id="seg_a" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.317979px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 37.115086,266.93894 10.577457,-7.64727 H 169.33333 l 10.57746,7.64727 -10.57746,7.64727 H 47.692543 Z"
id="seg_h" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.40603px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 34.105603,31.755883 8.22773,10.57745 V 254 L 34.105603,264.58333 26.458333,254 V 42.333333 Z"
id="seg_b"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.40603px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 182.27227,31.75588 190.5,42.33333 V 254 L 182.27227,264.58333 174.625,254 V 42.33333 Z"
id="seg_e"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 55.5625,42.333333 -7.937501,10.583333 10e-7,82.020834 10.583333,10.58333 h 39.6875 l 10.583337,-7.9375 -10.583337,-7.9375 H 71.4375 l -7.9375,-7.9375 0,-68.791663 z"
id="seg_c"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 55.5625,254 -7.9375,-10.58334 0,-82.02083 10.583333,-10.58333 h 39.6875 l 10.583337,7.9375 -10.583337,7.9375 H 71.4375 l -7.9375,7.9375 0,68.79166 z"
id="seg_f"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 161.39583,254 7.9375,-10.58334 0,-82.02083 L 158.75,150.8125 h -39.6875 l -10.58333,7.9375 10.58333,7.9375 h 26.45833 l 7.9375,7.9375 0,68.79166 z"
id="seg_g"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:#202040;fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 161.39583,42.333337 7.9375,10.58333 0,82.020833 -10.58333,10.58333 h -39.6875 l -10.58333,-7.9375 10.58333,-7.9375 h 26.45833 l 7.9375,-7.9375 0,-68.791663 z"
id="seg_d"
sodipodi:nodetypes="ccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

22
templates/index.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>VCD to 8 Segment SVG renderer</title>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<h4>Upload VCD file</h4>
<form method="POST" enctype="multipart/form-data" action="/upload">
<label for="file">VCD File:</label><input name="file" type="file" required/>
<input type="submit" value="Upload"/>
</form>
</body>
</html>

26
templates/setup.html Normal file
View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>VCD to 8 Segment SVG renderer</title>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<h4>Select signal</h4>
<form method="POST" enctype="multipart/form-data" action="/setup/{{ uuid }}">
<select name="signal">
{% for signal in signal_names %}
<option value="{{signal}}" {{"selected" if signal == default_signal else ""}}>{{ signal }}</option>
{% endfor %}
</select>
<input type="submit" value="Submit"/>
</form>
</body>
</html>

3
vcd8seg.cfg Normal file
View file

@ -0,0 +1,3 @@
UPLOAD_PATH = "/tmp/vcd_to_8seg"
SECRET_KEY = "changeme"
STATIC_FOLDER = 'static'

82
vcd_to_8seg_svg.py Normal file
View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
import copy
from os import path
import sys
import click
from bs4 import BeautifulSoup
import vcdvcd
@click.command()
@click.option('-s', '--signal', default=None)
@click.option('--svg-template', type=click.File('r'), default=(path.dirname(__file__) + '/img/8seg-template.svg'))
@click.argument('in_vcd', type=click.File('r'))
@click.argument('out_svg', type=click.File('w'))
def vcd_to_8seg_svg(in_vcd, out_svg, svg_template, signal):
with svg_template 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:
print('Signal {signal} not found. Available signals:', file=sys.stderr)
for sig in signal_names:
print(' ', sig, file=sys.stderr)
raise ClickException()
for time, value in sig.tv:
value = ('0' * int(sig.size) + value)[-int(sig.size):]
print(f'@{time}: {signal} = {value}')
yield [int(x) for x in value]
with in_vcd as f_in:
with out_svg as f_out:
f_out.write(str(concat_digits(list(parse_vcd(in_vcd.read(), signal)))))
if __name__ == '__main__':
vcd_to_8seg_svg()