Query: advanced search, command line tutorial: query language


pcb-rnd from version 1.1.3 features a flexible advanced search that helps the user selecting/unselecting objects that match a given logical expression. The core of the feature is the pcb-rnd query language. The same language is used in the programmable DRC (see also: a more formal description of the language).

The current document is a walk through of the practical aspects of the language. It starts with the simplest examples while working towards more complex cases.

A limited version of the functionality is accessible through a GUI wizard when using the GTK HID. A separate tutorial is dealing with that feature.


The query(act, expr) action creates the list called "@", which contains all objects of the design. Then it iterates over this list (if needed) and evaluates the query expression on each object. For each evaluation "act" is performed; "act" is one of:

The symbol @ in the query represents the iterator, or in other words, the current object we are checking. The engine iterates over all copper objects, silk objects, pins, holes, layers and nets. A simple query that selects all objects looks like this:

query(select, '@')
The actual query expression was a single @ in this case. This made the iteration happen, got the expression evaluated one each object. The result of each evaluation was the given object. Since these objects were all existing, valid objects, they were taken as logical value TRUE, thus for each the add-to-selection was performed.

Note: it's usually a good idea to write the expression in single quotes, because it may contain commas, double quotes and parenthesis that pcb-rnd's action parser may take as action syntax.

The same expression can ran with eval would print the result of each evaluation:

query(eval, '@')
This is visible on the terminal pcb-rnd was started from - on X, it's a good idea to start a terminal and run pcb-rnd from that instead from a menu or icon. The rest of this tutorial will use the eval query because it's easier to include the result of an eval than of a selection. Most examples will specify the query expression only, without quotes - the reader should add the query(eval, ' ') part.

iteration vs. evaluate once

If an expression does not reference the @ object, no iteration is performed and the expression is ran only once:
query(eval, '1')
This will calculate the value of 1 and prints it to the standard output. Since there's no iteration, this can not result in changes in selection. However, it makes it easy to demonstrate some basic concepts of the query language.

Note: if @ is present multiple times in the expression, it's still only one loop over all objects. When evaluating the expression for a given object, all instances of @ are substituted with the same object in that iteration.

grammar: arithmetics and logics

For example the integer and floating point numbers and the usual arithmetic and logical operators work as expected:
expression result explanation
42 42 the integer value 42
3.14 3.14 the floating point value 3.14
10 mil 254000 a number with a unit suffix is converted to pcb-rnd's internal coordinate unit (nanometers)
1+2 3 sum of 1 and 2
2*4 8 multiplication
47/4 11 integer division (because both operands were integers)
47/4.0 11.75 floating point division (because at least one of the operands was a float)
(1+2)*5 15 parenthesis works as usual
1 && 0 0 logical AND - the result is 1 if both operands were TRUE, 0 else
1 || 0 1 logical OR - the result is 1 if either operand was TRUE, 0 else
!2 0 logical NOT - 2 is non-zero, so it is TRUE, negate this to get the result (FALSE, which is 0)
4 > 2 1 because four is greater than two; all the usual relational operators work: == is equal, != is not-equal, <, <=, > and >=.

grammar: object properties

Object have named properties, e.g. the thickness of a line (or arc, or trace in general) is called ".thickness", so the thickness of the current object is:

Next, we can already select all traces thicker than 10 mil:

	query(select, '@.thickness > 10 mil')

Because logical expressions are available, it's easy to select all medium-thick lines:

	(@.thickness >= 10 mil) && (@.thickness <= 30 mil)

or any trace that's too thin or too thick:

	(@.thickness < 10 mil) || (@.thickness > 30 mil)

or traces that don't match our preferred 8, 10 and 15 mil thickness values:

	(@.thickness != 8 mil) && (@.thickness != 10 mil) && (@.thickness != 15 mil)

grammar: invalid value

But how comes an expression like '@.thickness > 10 mil' works while @ iterates over non-trace objects, like layers, nets or subcircuits that clearly have no thickness?

The answer is the invalid value, which is neither true nor false. If a property does not exist in a given object, the result is the invalid value or invalid for short. The invalid is treated specially:

When @ refers to a non-trace object, @.thickness is invalid and '@.thickness > 10 mil' is evaluated t invalid. If the result is not TRUE, the requested action is not performed.

The invalid value is generated only when the expression is valid but the actual object doesn't have the feature needed. If the expression is wrong, an error is generated and the expression stops evaluating immediately.

grammar: more properties

Some properties are a single numeric value, like thickness, or the .name of a layer (which is a string) but others are objects. For example the .layer property of a line or arc refers to the layer object the given line or arc is drawn on. These in turn can be combined, and the "name of the layer the current object is on":

Referencing non-existing properties yields an error, e.g. @.thickness.layer is an error - in contrast with @.layer, which may evaluate to the invalid value (e.g. for vias or nets, because they don't have layers). The difference is that thickness can not have a layer ever (thus is an error) while @.layer is either applicable or not, but potentially could work, this when it doesn't, it's only an invalid value.

Further useful property combinations: TODO