pcb-rnd knowledge pool
render_script by Tibor 'Igor2' Palinkas on 2020-01-22
Tags: insight, render, script, layer, group, order, GUI, export
Abstract: After version 2.3.1, pcb-rnd switched from hardwired to scripted render coordination. This provides more flexibility on determining the order of rendering different layer groups and annotations.
How it worked before
There's a function in the code that is responsible for driving the render. It's called draw_everything(). It was derived from a code we inherited from the 2011 version of gEDA/PCB (but I guess it did not change much in that project). It got almost completely rewritten incrementally, because of API changes, but the essence, the logic of the code did not change: it determines in what order layers are rendered. In other words this is the function that makes mask appear between copper and silk.
It is a rather complicated piece of code, in the sense that literally decades of historical "do foo before bar, but don't do baz if..." accumulated in a long C function.
I got feature requests like:
1. hole under mask
"I use microvias and mask covers my via holes in reality, but pcb-rnd renders it as if holes pierced through mask!"
2. hole vs. silk
Similar story on silk not being drilled, but holes are covered with silk (some fabs avoid this and they do remove parts of the silk graphics above holes, so you don't always see this)
3. paste vs. non-copper
It's also an interesting question where to render paste; in the process, it goes over everything, as it's put on last, and we render it like that. But that doesn't necessarily make visual inspection easy, when a silk line accidentally gets under a paste object. (Well, we should catch that with DRC anyway).
I didn't want to complicate that convoluted C code any further so these feature request generally ended up as "just make this scriptable" in the TODO.
II. What's new
New is a config node that holds a simple script that tells the code what layer groups and other things to render, in the order the script is running. It can also execute some rendering step upon conditions, for example some things (like subcircuit marks) should appear only on screen, not in exports.
We have a new, very small and simple Domain Specific Language for this script, designed to be extremely fast to execute (it runs on every rendering, e.g. for every frame when you are panning!) and to avoid the possibility of accidentally creating infinite loops.
Documentation is available as a section of the user manual.
You can find the example script, which is the original C code translated to this script language 1:1, in pcb-conf.lht in the render_script string.
Note: we still have the old C code as well; it is used as fallback if the script is not available or has runtime errors.
III. How does it affect you
If you are happy with the rendering order we always had, this does not affect you at all.
If you want to change the rendering order, you can easily do that changing the script via config. Since this is a plain config node, you can do this in your user config, or if you have only one project or board that wants it differently in the project file or board file.
Note: the scope of this effort is limited to the coordination of rendering, more specifically to the _order_ of layer groups and other grouped things are rendered. So it won't let you change how things look and won't give you any fine grain control over graphical details, as these things are all in the low level render code.
Warning: if you change your render script, you will need to update it if upstream adds new things. It doesn't happen very often, but sometimes it does. For example when new layer types appear. If you forget to update your custom script, it may simply not render the new features. It's a bit like the problem we had with custom menu files before the menu patch system, except that these scripts are real small and there are much much fewer changes in upstream so manual maintenance of such custom scripts sound viable.
IV. History of this feature
Originally pcb (and its predecessor, router) back in the early 90s, had only a few layers to deal with - it was pretty much a "copper only, 2 layer board I could etch in the kitchen" kind of editor. Then new layers got added gradually, with a lot of implications and automatism. That's why it was only the silk layer that could be edited in gEDA/pcb, not mask or paste or fab or assy. Or in other words, any layer beyond copper was special cased in one way or another.
These special case layers got added over more than 2 decades of project history. Plus more export targets got gradually added too, which yielded new combination of special cases: gerber wanted one layer group per file while ps and png wanted to combine them. The rendering code grew spontaneously, seemingly without much though about long term code maintaining aspects.
This led to a real long and convoluted piece of code that was also fragile: if you changed anything, you risked some special case rendered wrong on GUI or on some rarely used export target.
In the same time, there were legit feature requests that could only be solved by complicating this piece of code with even more options and conditions and branches. Since this was already a complicated and fragile and essential/central piece of code, I decided not to add the complication required for those features.
Making the whole thing scriptable is a nice way to escape from the ever-growing spaghetti: instead of a dozen of special boolean settings such as "render hole below mask", we can keep things simple and flat and still let the user change rendering order. In fact it's even more flexible this way as the user can use a lot more combinations of ordering, things the programmer did not think about and could not add a boolean switch for.