Skip to content

Instantly share code, notes, and snippets.

Created November 1, 2017 08:30
What would you like to do?
for exporting SVG panels to VCV plugin source files
# for exporting SVG panels to VCV plugin source files
# Version: 1
# Support contact email: /dev/null
# License: CC0
import sys
import os
import re
import xml.etree.ElementTree
help = """usage: python SVGFILE > source.cpp
This hacky script converts an .svg panel file to a C++ source file to create VCV module.
The panel must be set up in a very specific way using Inkscape.
1. Create a layer called "widgets". You may make it invisible when saving. This script will still read them.
2. In that layer, create a rectangle for every widget. Set the fill color accordingly. Alpha is ignored.
- params: red #ff0000
- inputs: green #00ff00
- outputs: blue #0000ff
3. The top-left position of the rectangle is what matters. Size doesn't, however you may want to match it to the size of the widget for alignment purposes.
4. Set the ID of the element with the enum the widget will refer to, excluding _PARAM, etc.
5. Run the script, and an entire C++ file will be generated on stdout which is a better starting point than nothing at all.
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
if len(sys.argv) < 2:
def slugize(name):
if name[0].isdigit():
return "_" + name
return name
dpi = 75
scale = 1
# scale = dpi / 25.4
filename = sys.argv[1]
basename = os.path.basename(filename)
slug = slugize(os.path.splitext(basename)[0])
eprint("slug: %s" % slug)
tree = xml.etree.ElementTree.parse(filename)
root = tree.getroot()
width = root.get('width')
height = root.get('height')
# width = float(re.findall('[\d.]+', width)[0])
# height = float(re.findall('[\d.]+', height)[0])
# width *= dpi
# height *= dpi
# hp = round(width / 15)
eprint("width: %s" % width)
eprint("height: %s" % height)
# eprint("hp: %d" % hp)
# Find widgets in tree
params = []
inputs = []
outputs = []
lights = []
groups = root.findall(".//{}g")
eprint("Found %d groups" % len(groups))
for group in groups:
label = group.get("{}label")
if label == 'widgets':
rects = group.findall(".//{}rect")
eprint("Found %d rects in layer called widgets" % len(rects))
for rect in rects:
label = rect.get('{}label')
if label is None:
label = rect.get('id')
eprint("Warning: %s has no label, using id" % label)
id = slugize(label).upper()
style = rect.get('style')
color_match ='fill:\S*#(.{6});', style)
color =
x = float(rect.get('x')) * scale
y = float(rect.get('y')) * scale
x = round(x, 3)
y = round(y, 3)
widget = {'id': id, 'x': x, 'y': y, 'color': color}
if color == 'ff0000': # Red
if color == '00ff00': # Green
if color == '0000ff': # Blue
if color == 'ff00ff': # Magenta
params = sorted(params, key=lambda widget: widget['y'])
inputs = sorted(inputs, key=lambda widget: widget['y'])
outputs = sorted(outputs, key=lambda widget: widget['y'])
lights = sorted(lights, key=lambda widget: widget['y'])
# Print output
#include "Tutorial.hpp"
struct %s : Module {\
""" % slug)
# Params
print(" enum ParamIds {")
for w in params:
print(" %s_PARAM," % w['id'])
print(" NUM_PARAMS")
print(" };")
# Inputs
print(" enum InputIds {")
for w in inputs:
print(" %s_INPUT," % w['id'])
print(" NUM_INPUTS")
print(" };")
# Outputs
print(" enum OutputIds {")
for w in outputs:
print(" %s_OUTPUT," % w['id'])
print(" NUM_OUTPUTS")
print(" };")
# Lights
print(" enum LightIds {")
for w in lights:
print(" %s_LIGHT," % w['id'])
print(" NUM_LIGHTS")
print(" };")
void step() override;
void %s::step() {
%sWidget::%sWidget() {
%s *module = new %s();
setPanel(SVG::load(assetPlugin(plugin, "res/%s")));
addChild(createScrew<ScrewSilver>(Vec(15, 0)));
addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
addChild(createScrew<ScrewSilver>(Vec(15, 365)));
addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));\
""" % (slug, slug, slug, slug, slug, slug, basename))
# Params
if len(params) > 0:
for w in params:
print(" addParam(createParam<Davies1900hBlackKnob>(mm2px(Vec(%g, %g)), module, %s::%s_PARAM, 0.0, 1.0, 0.0));" % (w['x'], w['y'], slug, w['id']))
# Inputs
if len(inputs) > 0:
for w in inputs:
print(" addInput(createInput<PJ301MPort>(mm2px(Vec(%g, %g)), module, %s::%s_INPUT));" % (w['x'], w['y'], slug, w['id']))
# Outputs
if len(outputs) > 0:
for w in outputs:
print(" addOutput(createOutput<PJ301MPort>(mm2px(Vec(%g, %g)), module, %s::%s_OUTPUT));" % (w['x'], w['y'], slug, w['id']))
# Lights
if len(lights) > 0:
for w in lights:
print(" addChild(createLight<RedLight>(mm2px(Vec(%g, %g)), module, %s::%s_LIGHT));" % (w['x'], w['y'], slug, w['id']))
Copy link

Strum commented Nov 11, 2017

I'm trying to get this working.

I'm getting an error when trying to run the script.

C:\Program Files\Inkscape>python
File "", line 30
print(*args, file=sys.stderr, **kwargs)
SyntaxError: invalid syntax

The description of how to use it doesn't include the colour for lights in the list , though it's there in the script, you might want to add it.

Perhaps an example of how you call it might be good in the description as well.

Copy link

Strum commented Nov 11, 2017

Found the usage message, but maybe including which version of python it's for would be good.

Maybe the error is a version conflict issue.

Copy link

jeremywen commented Dec 30, 2017

Notes for people trying to use this

  • This Requires Python Version 3
  • Set the Label and NOT the ID of each rectangle like the usage says.
  • 'ff00ff' (Magenta) is for lights
  • May need to remove mm2px call if SVG is in pixels

Copy link

phdsg commented Apr 7, 2018

@AndrewBelt i saw the widgets in the core panels and was wondering if you plan on including this script (or the updated version) in the template repo in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment