pcb-rnd knowledge pool


Why do we support 3 different gEDA/PCB .pcb formats?

fmt_pcb_nano1 by Tibor 'Igor2' Palinkas on 2016-09-04

Tags: insight, file format, format pcb, nanometer, precision, mm, mil, human, readable, unit

node source



Abstract: The reason for supporting 3 different gEDA/PCB .pcb formats. Rationale for human readable units and nanometer units; historical background on the centimil unit.

  Imported from the mailing list archives.

(History/terminology: back then I forked from the 2011 version (last stable release of mainline. There's also a 2014 public snapshot available, then the numerous git branches.)

On Sat, 3 Sep 2016, Chris Smith wrote:

> Have I missed something? I thought pcb mainline switched to nanometres some time ago, so why is it even using mils?

Yup, it did. It's still using mils & centimils in the .pcb file format in some fields, in some versions.... It's a mess. I've been tracing this back to the last stable release (2011) to make sure I don't mess up compatibility.

First, there's the old .pcb format and the new .pcb format. The old was probably used between 2007 and ~2010. It has () for blocks. The new got (probably) introduced by the time they did the nanometer transition and it uses the [] for blocks. The file loader handles both, but the writer emits only the new format. So pcb-rnd and mainline are not going to be save-compatible with the old (pre-nanometer?) versions.

In theory the loader accepts values with units, so if you write 1 mm or 1 inch in the file, it's 1 mm or 1 inch, without rounding errors. On the input (after my last fix) this is even true - this bug I fixed is still present in the 2014 snapshot [of mainline] (I don't know about git versions).

In the format, another option is to write a number without unit, which is then interpreted as centimils. You generally wouldn't do this. Unfortunately the whole file write code insist on writing the unitless centimil numbers using %mr - as integer! This is the main reason why a full round trip (save a proper pcb then load it again) introduces rounding errors whatever the load code does.... And this is why it happens to mm values, and not mil values. And yes, this is like that in the 2014 snapshot too (still no idea about git).

> Did pcb-rnd fork before this?

Nope, it's all inherited stuff - until today I didn't change anything in the .pcb file format or pcb-printf implementation regarding to these.

Tested mainline-2014, it does have similar rounding errors on your files. Mainline git may have come up with a fix for this since 2014, I'm not really following that.

Now, the important part: how I am fixing it.

01. In theory we could write using %mI, which is the internal unit, which is nm. We won't do this, because this has two big disadvantages: it exposes the internal units, so if we ever need to switch again, we break the file format (or have to update the writer code again); the bigger problem is that it's absolutely not human readable. This would be an unitless integer value, which would also confuse mainline loaders which would think it's in centimils - as long as they want to be compatible with the mainline version from the past few years...

02. We could insist on using the centimil format with enough decimal digits that it doesn't break. I have no idea to what extent it would break compatibility... I think it would cause the same rounding error with the 2014 version, as it would use the integer version after loading the value. But if we change anything, we should probably change to something more natural/readable than centimils... So this possibility is out.

1. There's a %$mS format, which always prints the unit and uses the "most natural" unit; this means it keeps 1 mm as 1 mm and 1 inch as 1 inch. If you have something like 1.123456 mm, it's somewhat "random" whether it ends up as mm or mil, depending on which one gives the saner output. We should use this. But...

2. By default, it prints only for 2 decimal digits, which in turn leads to precision loss - don't ask why, it's still inherited code. So the proper variant would be %6$mS - just enough digits to represent the smallest increment the core can handle in any appropriate unit. In other words, this length would guarantee we don't introduce errors by writing a value. But...

3. I don't want to see "1.000000 mm" all around. Especially if we already use "natural unit" so we most often end up with real round numbers. So I added a new feature in pcb-printf: %06$mS does the same as %6$mS, but also truncates excess trailing zeros, so 1 mm ends up as "1.0 mm".

4. Now this would be almost good enough, but this whole topic is risky: with the slightest wrong move we could break the file format and lose save-compatibility with whatever version of mainline a new pcb-rnd user would switch from. I am pretty sure the first time an user can't load a pcb-rnd edited .pcb in mainline, he loses all trust in pcb-rnd. So instead of just replacing the original "file format mandated" %mr, I am going to provide different .pcb file format options on save:

A. a natural unit .pcb; this would be the default .pcb long term. It would save using %06$mS; as far as I can tell, this would work with any mainline starting at least from 2011. This uses unit suffixes and "random" units.

B. a "compatibility" .pcb, which uses the current %mr, just in case someone really has a version from between 2010 and 2011 that doesn't handle units correctly on load. This means no unit suffixes and everything in centimil.

C. a nanometer unit .pcb; this would use nanometer for all numbers with unit suffixing. We need this because we lose easy script-processing with A.: your external scripts would need to understand like 9 different pcb units, and if the unit list is extended in pcb, all scripts would need to be upgraded too.... Instead, if you know you are going to feed a script, you could just use nanometers and lean back. (Note: other users have been complaining on the ML about mainline and script processing and solution A., so I'm not the only one worrying about this. In this, we'll be a bit more advanced than mainline, where you can't choose this runtime, afaik).

5. new export or IO formats, like lihata board, will probably have option A. and C. for the same human-read vs. script-processing reasons.

More details, imported from the mailing list archives.

Script-readable, nm suffixed integer coords, independently of the internal numeric representation/unit. Round trip worked without rounding error.

When looking at the file in a text editor, please note:

More details: testing method, imported from the mailing list archives.

Testing the human readable units from the command line:

1. svn up
2. make
3. cd tests/pcb-printf
4. make
5. ./prcli "%.08mH" "0.254mm"

The heuristics is pretty simple:

- it converts the value to mm first; if it's out of the "convenient range" of 0.001 .. 1000 mm, it changes to um or m (metric value)

- it then converts the value to mil; if it's bigger than 1000 mil, it converts to inch (imperial value)

- it counts how many non-zero decimal digits the value the metric and the imperial value has and picks the one that has less (i.e. the more round)

- if they look the same, it prefers metric - it is because the internal base unit (nanometer) can be converted to metric forth and back without loss (in theory)

Please test it and find corner cases where it is not doing what you expect it to do.


0.254mm -> 10.0 mil
0.251mm -> 0.251 mm
1.12mil -> 1.12 mil
1.3in -> 1.3 in
0.3in -> 300 mil
1.2m -> 1.2 m
0.1m -> 100 mm
0.0005mm -> 0.5 um
0.01mil -> 0.01 mil
0.001mil -> 0.025 um (um and mil roundness match, metric preference)


1.123mil -> 0.028524 mm - it is because the inherited code uses doubles carelessly and there are rounding errors making the actual value 1.1229999 on the input conversion which is then not rounder in mil than in mm, and the mm preference triggers.

I think this would be addressed only when we get rid of floats in a later development cycle.