Death to helper.py, Long live SvgHelper

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 … ???

Dunno, was just the license file that was in the template I used. I think MIT is basically as permissible as it gets, but I’m not a lawyer either heh.

I will post back here when I have something interesting to show.

neat :slight_smile:

1 Like

One thing I have discovered so far: since I need to reload the SVG panel anyway, so that VCV Rack can redraw it, I can use the same object to find all my hidden control objects. I already do a similar traversal in my Tube Unit module where I dynamically hide/show part of the SVG depending on whether any cables are connected to the input ports:

    TubeUnitWidget(TubeUnitModule* module)
        : tubeUnitModule(module)
    {
        setModule(module);
        svgPanel = createPanel(asset::plugin(pluginInstance, "res/tubeunit.svg"));
        setPanel(svgPanel);
        if (svgPanel && svgPanel->svg && svgPanel->svg->handle)
        {
            // Search for my special <path id="audio_emphasis_path" ... />
            // I toggle this path's visibility as needed, depending on whether there are audio inputs.
            // For now we capture a pointer to this path.
            for (NSVGshape* shape = svgPanel->svg->handle->shapes; shape != nullptr; shape = shape->next)
                if (!strcmp(shape->id, "audio_emphasis_path"))
                    audioEmphasisPath = shape;
        }

        // ... more code here ...
    }

I mention this because it’s more efficient to let VCV Rack load the file and parse the XML one time, and use the same in-memory data structure for both rendering and finding the locations of controls and ports.