Death to helper.py, Long live SvgHelper

Hey all

I’ve written a little ModuleWidget mixin called SvgHelper which lets you quickly find all the SVG elements whose ID follows a certain pattern and get their positions. In this way you can skip helper.py all together and just generate inputs/outputs/lights/etc directly from the SVG. As you’re iterating you can just update the SVG and rebuild to see the results. It’s pretty glorious…

Here’s some examples from the README:

Prefixes

#include "SvgHelper.hpp"

struct MyModuleWidget : ModuleWidget, SvgHelper<MyModuleWidget> {
    MyModuleWidget(MyModule* module) : SvgHelper<MyModuleWidget>(this) {
        setModule(module);
        loadPanel(asset::plugin(pluginInstance, "res/MyModule.svg"));
        
        // find a shape with a specific name
        auto lightPos = findNamed("StatusLight")
        addChild(createLightCentered<MyLight>(lightPos), module, MyModule::STATUS_LIGHT));
        
        // find all shapes with a specific prefix
        forEachPrefixed("input_", [&](int i, Vec pos) {
            addInput(createInputCentered<MyPort>(pos, module, MyModule::INPUT_1 + i));
        });
        
        // find all shapes matching a regular expression
        forEachMatching("output_(\\d+)", [&](vector<string> captures, Vec pos) {
            int i = stoi(captures[0]);
            addOutput(createOutputCentered<MyPort>(pos, module, MyModule::OUTPUT_1 + i));
        });
    }
};

Here’s a short video of me updating some widgets real quick:

Let me know what you think!

Also, when I load the project in CLion, it has no clue about the Rack SDK which makes working on the code a little difficult. I would suuuuper appreciate if someone sent me a PR with some CMake magic to make CLion intellisense working for the Rack API, etc. (eyes @baconpaul lol)

9 Likes

This is a really cool idea. And recently this sort of thing has been on my mind. I know many developers here don’t use SVG at all in their module designs, because it is frustrating keeping SVG in sync with C++ code.

Over the last week I’ve been pondering the feasibility of creating an IDE for designing VCV Rack panels. I don’t really care for Inkscape because it feels clumsy, and I keep being unpleasantly surprised by how obtuse some of its behaviors are.

I still use SVG for my panels, but I have started coming at it from another angle: writing Python scripts that generate shapes in my SVG. For example, I wrote this script to generate a grid of interlocking pentagons with color gradients for my next module’s panel. I use consistent formulas inside the C++ code for placing the controls and ports inside the pentagons.

The part I still use Inkscape for is to add text to my designs. As frustrating as it is, Inkscape does a good job of converting fonts to stroked paths. That said, I’m tempted to reverse engineer these calculations and roll my own, because getting the text positioned correctly is still super irritating and tedious.

So, just to brainstorm here, I could see an incremental path toward a VCV Rack panel IDE, and it could start with what you’ve done here. Imagine combining your idea of extracting positions from SVG with something else that can generate the SVG in the first place. Also imagine something that can allow you to create the kind of SVG files appropriate for this tool in particular and VCV Rack modules in general. This hypothetical IDE could be quite limited compared to InkScape, but perhaps far more productive for the kinds of things we want to do.

Has anyone else thought along these lines? I could see myself working to create such a project, but I still haven’t convinced myself that it would be worth the effort in the long run.

4 Likes

i would kill for such a tool.

1 Like

So chromium has super good SVG support. When I need to edit SVGs I use boxy which is based on chromium

I only share this because it makes me think I wonder if vscode could do what you want with a smidge of JavaScript

I layout all my panels in code so this may be a dumb idea

1 Like

I pretty much use the classic method - make a panel in some program, export an SVG that works. The key thing for me is to use a “proper” screen design program (there are many), and don’t use a “vector drawing program” (there are many of those, too).

I made a sharable “sticker sheet” that has all the controls I under (knobs, switches, ports, etc…). Then when I want to make a real panel I insert those controls, position them, add the text. All of which is nearly trivial in a screen design program. The extra step of having to copy coordinates from the tool to code doesn’t take long.

So, my process is:

start with a blank panel. put all the controls and text on with simple code. It will look very ugly. when the module is “done”, I make a panel in Adobe XD, using the shared component library (sticker sheet) that I made long ago. I send screen shorts of the panel to testers. When the testers are satisfied, I hide all the controls that will be done in code (knobs, ports, switches), and render out my “panel”. They I copy over all the component locations by hand. Usually I find something wrong, and iterate again.

Now, I know everyone likes a different workflow, and that’s cool. I will say that it takes me about a month to make a module, and of that time it takes about a day to make a panel this way. For me, if I were looking for a way to make module building easier, it probably would not be in panel making. Of course my modules are super basic (ugly).

1 Like

I’m doing the same thing. I have all the actual Rack widget SVGs in a big SVG and I pull them over to my panels. Here’s what Facade looks like in Inkscape. Each port widget is named like “in_1” or “out_1” or “label_1”, etc.

Then I just use SvgHelper to automatically grab all of their positions.

I personally don’t have any difficulty using Inkscape to make SVG. It was the update workflow that was disincentivizing me from tweaking things. Now there’s no overhead so I don’t think I need a custom Rack module editor at this point.

1 Like

Yeah, that seems a real pain if you use helper.py. Your solution sounds less onerous for sure.

If Inkscape works for you, that’s cool. No reason to change. For me, not knowing any of these tools much I chose to learn some that other people use. I have never heard of Inkscape being used for screen design, but then people do use Illustrator which is similar-ish.

In a big project (unlike VCV modules!) It’s super handy to be able to add links for external components, rather than copies. So if you update the original it updates in all the screens that use it. But that isn’t super compelling for what we do here.

Personally I “hacked/changed” the “helper” python script to generate a file with positions instead of touching the rest of the code.

I still have to re-compile after changing positions. (In inkscape)

Call like this:

./update_positions.py res/your_module.svg src/your_module_pos.inl

script here (had to rename to txt to attach): update_positions.py.txt (6.9 KB)

2 Likes

That’s super helpful just for the sample code of processing an SVG file from Python. Every few years I write some Python code that parses XML, and I have to relearn how to do everything (especially XML namespaces) with dozens of Google searches. I’m going to hang on to this one as a great cheat-sheet.

1 Like

Hora Music did, but it’s gone now.

Where I work we call these “blocky languages”. Most of us who have worked on one or two don’t think much of them…

There’s this:

I don’t know how good it is and it’s been more than a year since the last update.

I watched the Hora video, and it’s headed in the right direction (rapid visual feedback), but I don’t think it addresses the main problem I have: precision layout. I felt sad when I saw him hand-placing the components, and he never did fix the horizontal alignment of the input and output jacks. He copied and pasted the y-coordinate to fix vertical alignment, which is exactly the same thing I could do in my C++ code. The only advantage is you can see the result immediately, instead of having to compile, install, and run VCV Rack.

Where I spend the most time in panel design is the change-build-run loop. I keep changing the code, running make install, opening VCV Rack, then closing VCV Rack again. It’s not ideal, but it’s tolerable. It’s certainly better than moving things around on the screen with my mouse. With code, I can align everything perfectly in a grid. I just need to confirm that it looks right on the screen. Then I know for sure that someone with better eyes than mine won’t be annoyed by sloppy placement.

So I keep coming back to the conclusion that using formulas in code is still the best possible solution, but any real improvement would be either

  • changing VCV Rack so that it can “hot-load” changes to a plugin without closing and re-opening it.
  • some other tool that can emulate VCV Rack rendering while staying open as I make changes to the code.

Bonus points if I can compare before/after a change, seeing both on the screen at the same time.

I agree with this sentiment. The main problem I see is that if you have to use existing functional blocks, you will only be able to create things very much like what already exists in the 2500+ plugins out there. Or you will have to create a new block, which will certainly be more work in the long run than just writing the code in your module.

You can do this with SvgHelper, today.

I really recommend people just try it…

1 Like

Interesting! I suppose there is some way to trigger your module to reload the SVG file?

You can just at anytime use this helper to read your SVG file and find the positions of your widgets which you can then update. Put a right-click menu on your module, use efsw to watch for changes, etc.

Also, you can combine this with procedurally generating positions too.

Let’s say you want to draw a circle of widgets, but you haven’t decided on how many widgets will make up the circumference. Well you can just place a widget where the center would be, then in C++, generate the positions for the actual widgets based on radius and the center, etc.

Regarding precise positioning I think you should look into the alignment pane in Inkscape more closely as it has quite a lot of features for positioning things relative to each other, evenly spacing them, centering one widget relative to two others, and so on.

Finally there are scripting solutions for Inkscape, but I just don’t know that they’re necessary.

2 Likes

Yes, I have used those, and they can be helpful. However, it is limited. Here is an example of what I mean by alignment: create a grid of interlocking pentagons with groups of controls inside them:

Inkscape can help do this, but it can’t fully comprehend the whole geometric picture I’m trying to create. It’s far easier to explain this with code than using alignment tools in Inkscape. It’s possible writing the code won’t save me very much time, but I know for sure it is less stress for me and it gives mathematically perfect results.

I really like this idea! We should remove/disable the item from the menu before releasing the module, or have some super-secret way of turning it on and off. I think using your approach of parsing the SVG in your widget class, algorithmic generation of the SVG in the first place, and a hot-load option, could really accelerate the panel design loop. Very cool! I think I will try this for my next module.

2 Likes

I started thinking about this some more, and something occurred to me. Generally components are placed by the widget class constructor. It’s not obvious how the hot-load will work to move things around. Without deleting an instance of my module and creating another one, I don’t see how that constructor is going to be called again. It seems like hot-load will have to find existing components and move them, which is different from creating them in the first place. Am I making sense? Am I missing something obvious?

[EDIT] I suppose the widget constructor will create all the knobs, sliders, ports, etc., but at something like (0, 0). Then it immediately can call a function HotLoad which reads the SVG and moves everything to the correct location. Then my menu item can just call the same HotLoad function. I think this will work, assuming there is a way to move the location of existing controls and ports.

Also, something needs to tell VCV Rack to re-parse and re-render the SVG itself, graphically. It’s not obvious how to do that either.

Yeah the SVGHelper will only help with finding the positions in the SVG file; so that wont help with updating the panel graphic and you need to implement a mechanism for updating widget positions yourself.

I keep references to the widgets as I create them.

edit: For updating the panel, looks like you can just call setPanel again;

void ModuleWidget::setPanel(widget::Widget* panel) {
	// Remove existing panel
	if (internal->panel) {
		removeChild(internal->panel);
		delete internal->panel;
		internal->panel = NULL;
	}

	if (panel) {
		addChildBottom(panel);
		internal->panel = panel;
		box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
		// If width is zero, set it to 12HP for sanity
		if (box.size.x == 0.0)
			box.size.x = 12 * RACK_GRID_WIDTH;
	}
}
1 Like

This is exciting! I’m going to create an experimental branch in my Sapphire repo on GitHub and try this out. I will post back here when I have something interesting to show.

I wonder about mixing your MIT license code with GPLv3+ as VCV Rack seems to prefer for open-source modules… I’m not a lawyer, so … ???