Task 251: .GBR File Format

Task 251: .GBR File Format

1. List of All Properties of the .GBR File Format Intrinsic to Its File System

The .GBR file format (Gerber RS-274X) is an ASCII-based, UTF-8 encoded, human-readable vector image format for PCB layer data transfer. It is not a file system per se but a stream of commands for defining 2D bi-level images (e.g., copper traces, silkscreen). Its intrinsic properties include structure, encoding, coordinate system, and parameter blocks that define the image. Based on the official Ucamco specification (Revision 2021.02), here is a comprehensive list of all intrinsic properties that can be parsed from a .GBR file:

  • Encoding: UTF-8 ASCII (portable, human-readable, uses printable 7-bit ASCII characters with Unicode escapes).
  • Structure: Stream of commands terminated by * (asterisk); lines separated by LF (line feed); optional G04 comments; file ends with M02* (end of job).
  • Coordinate System: Absolute (default) or incremental (deprecated); integer coordinates scaled by format specification; origin at (0,0); no rotation or mirroring intrinsic (must align externally).
  • Units: Specified by %MO* block (INCH or MM; default INCH if omitted).
  • Format Specification (FS): %FS* where notation is LAXnnYnn (LA = leading zeros omitted, absolute; nn = digits after decimal); specifies precision (e.g., LAX35Y35 for 3 integer + 5 decimal places).
  • Image Polarity (IP): %IPPOS* or %IPNEG* (positive/dark or negative/light; default POS).
  • Aperture Definitions (AD): %AD,* (defines shapes like C for circle, R for rectangle; params in units; e.g., ADD10C,0.1 for 0.1 unit diameter circle).
  • Aperture List (AL): %AL* lists aperture numbers (optional, for external tools).
  • Step and Repeat (SR): %SRX IX Y IY* for panelization (optional).
  • File Attributes: X2 attributes via %TF.,* (e.g., %TF.FileFunction,Copper,L1,Top* for layer function; %TF.FilePolarity,Positive*).
  • Object Attributes: Attached to graphics (e.g., %TO.Pad,1* for pad netlist; optional, revision 2016.09).
  • Aperture Attributes: %TA.AperFunction,Conductor* (e.g., for routing or via).
  • Interpolation Modes: G01 (linear, default), G02/G03 (circular CW/CCW), G75 (multi-quadrant arcs, default).
  • Operations: D01 (draw), D02 (move), D03 (flash aperture); regions via G36/G37.
  • Load Polarity (LP): %LPF* or %LPD* (film or dark; deprecated).
  • Mirror Image (MI): %MIA0* (no mirror, default; deprecated).
  • Scale Factor (SF): %SF,* (1.0 default; deprecated).
  • Offset: %OF,* (0 default; deprecated).
  • Rotation: %RO* (0 default; deprecated).

These properties are extracted from parameter blocks (%...*) and state commands (G/D/M). Graphical data (draws/flashes) references these but is not a "property" per se; the format is compact, with no binary data or compression intrinsic.

3. Ghost Blog Embedded HTML JavaScript for Drag-n-Drop .GBR File Parsing

This is a self-contained HTML snippet with embedded JavaScript for Ghost blog posts. Paste it into a Ghost post (use HTML card). It allows drag-and-drop of a .GBR file, parses the properties, and dumps them to a <pre> block on screen.

Drag and drop a .GBR file here to parse properties

4. Python Class for .GBR File Handling

This Python class opens a .GBR file, parses and prints the properties, and has a write method to reconstruct a basic file from parsed properties.

import os

class GBRParser:
    def __init__(self, filename=None):
        self.properties = {
            'encoding': 'UTF-8 ASCII',
            'structure': 'Command stream terminated by *',
            'coordinateSystem': 'Absolute (default)',
            'units': 'INCH (default)',
            'formatSpec': None,
            'polarity': 'POS (default)',
            'apertureDefinitions': [],
            'apertureList': [],
            'stepRepeat': None,
            'fileAttributes': {},
            'objectAttributes': [],
            'apertureAttributes': {},
            'interpolationModes': {'linear': True, 'circularCW': False, 'circularCCW': False, 'multiQuadrant': True},
            'operations': {'draw': 0, 'move': 0, 'flash': 0},
            'endOfFile': False
        }
        if filename:
            self.read(filename)

    def read(self, filename):
        if not os.path.exists(filename):
            raise FileNotFoundError(f"{filename} not found")
        with open(filename, 'r', encoding='utf-8') as f:
            content = f.read()
        self._parse(content)
        self._print_properties()

    def _parse(self, content):
        commands = content.split('*').map(lambda cmd: cmd.strip()).filter(lambda cmd: cmd)
        for cmd in commands:
            if cmd.startswith('%'):
                cmd = cmd[1:]  # Remove %
                if cmd.startswith('FS'):
                    self.properties['formatSpec'] = cmd[2:]
                elif cmd.startswith('MO'):
                    self.properties['units'] = cmd[2:]
                elif cmd.startswith('IP'):
                    self.properties['polarity'] = cmd[2:]
                elif cmd.startswith('AD'):
                    self.properties['apertureDefinitions'].append(cmd[2:])
                elif cmd.startswith('AL'):
                    self.properties['apertureList'] = cmd[2:].split(',')
                elif cmd.startswith('SR'):
                    self.properties['stepRepeat'] = cmd[2:]
                elif cmd.startswith('TF.'):
                    parts = cmd[3:].split(',')
                    self.properties['fileAttributes'][parts[0]] = ','.join(parts[1:])
                elif cmd.startswith('TA.'):
                    parts = cmd[3:].split(',')
                    self.properties['apertureAttributes'][parts[0]] = ','.join(parts[1:])
            elif cmd.startswith('G'):
                if '01' in cmd: self.properties['interpolationModes']['linear'] = True
                if '02' in cmd: self.properties['interpolationModes']['circularCW'] = True
                if '03' in cmd: self.properties['interpolationModes']['circularCCW'] = True
                if '75' in cmd: self.properties['interpolationModes']['multiQuadrant'] = True
            elif cmd.startswith('D'):
                if '01' in cmd: self.properties['operations']['draw'] += 1
                if '02' in cmd: self.properties['operations']['move'] += 1
                if '03' in cmd: self.properties['operations']['flash'] += 1
            elif cmd.startswith('M02'):
                self.properties['endOfFile'] = True

    def _print_properties(self):
        import json
        print(json.dumps(self.properties, indent=2))

    def write(self, filename):
        with open(filename, 'w', encoding='utf-8') as f:
            f.write('%FS' + (self.properties['formatSpec'] or 'LAX35Y35') + '*\n')
            f.write('%MO' + self.properties['units'] + '*\n')
            f.write('%IP' + self.properties['polarity'] + '*\n')
            for ad in self.properties['apertureDefinitions']:
                f.write('%AD' + ad + '*\n')
            for attr, val in self.properties['fileAttributes'].items():
                f.write('%TF.' + attr + ',' + val + '*\n')
            f.write('M02*\n')

# Usage
# parser = GBRParser('sample.gbr')
# parser.write('output.gbr')

5. Java Class for .GBR File Handling

This Java class uses java.nio for reading, parses the properties, prints to console, and writes a reconstructed file.

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class GBRParser {
    private Map<String, Object> properties = new HashMap<>();

    public GBRParser(String filename) {
        properties.put("encoding", "UTF-8 ASCII");
        properties.put("structure", "Command stream terminated by *");
        properties.put("coordinateSystem", "Absolute (default)");
        properties.put("units", "INCH (default)");
        properties.put("formatSpec", null);
        properties.put("polarity", "POS (default)");
        properties.put("apertureDefinitions", new ArrayList<String>());
        properties.put("apertureList", new ArrayList<String>());
        properties.put("stepRepeat", null);
        properties.put("fileAttributes", new HashMap<String, String>());
        properties.put("apertureAttributes", new HashMap<String, String>());
        Map<String, Boolean> modes = new HashMap<>();
        modes.put("linear", true);
        modes.put("circularCW", false);
        modes.put("circularCCW", false);
        modes.put("multiQuadrant", true);
        properties.put("interpolationModes", modes);
        Map<String, Integer> ops = new HashMap<>();
        ops.put("draw", 0);
        ops.put("move", 0);
        ops.put("flash", 0);
        properties.put("operations", ops);
        properties.put("endOfFile", false);
        if (filename != null) {
            read(filename);
        }
    }

    public void read(String filename) {
        try {
            String content = new String(Files.readAllBytes(Paths.get(filename)));
            parse(content);
            printProperties();
        } catch (IOException e) {
            System.err.println("File not found: " + e.getMessage());
        }
    }

    private void parse(String content) {
        String[] commands = content.split("\\*");
        for (String cmd : commands) {
            cmd = cmd.trim();
            if (cmd.isEmpty()) continue;
            if (cmd.startsWith("%")) {
                cmd = cmd.substring(1);
                if (cmd.startsWith("FS")) {
                    properties.put("formatSpec", cmd.substring(2));
                } else if (cmd.startsWith("MO")) {
                    properties.put("units", cmd.substring(2));
                } else if (cmd.startsWith("IP")) {
                    properties.put("polarity", cmd.substring(2));
                } else if (cmd.startsWith("AD")) {
                    ((List<String>) properties.get("apertureDefinitions")).add(cmd.substring(2));
                } else if (cmd.startsWith("AL")) {
                    ((List<String>) properties.get("apertureList")).addAll(Arrays.asList(cmd.substring(2).split(",")));
                } else if (cmd.startsWith("SR")) {
                    properties.put("stepRepeat", cmd.substring(2));
                } else if (cmd.startsWith("TF.")) {
                    String[] parts = cmd.substring(3).split(",");
                    ((Map<String, String>) properties.get("fileAttributes")).put(parts[0], String.join(",", Arrays.copyOfRange(parts, 1, parts.length)));
                } else if (cmd.startsWith("TA.")) {
                    String[] parts = cmd.substring(3).split(",");
                    ((Map<String, String>) properties.get("apertureAttributes")).put(parts[0], String.join(",", Arrays.copyOfRange(parts, 1, parts.length)));
                }
            } else if (cmd.startsWith("G")) {
                Map<String, Boolean> modes = (Map<String, Boolean>) properties.get("interpolationModes");
                if (cmd.contains("01")) modes.put("linear", true);
                if (cmd.contains("02")) modes.put("circularCW", true);
                if (cmd.contains("03")) modes.put("circularCCW", true);
                if (cmd.contains("75")) modes.put("multiQuadrant", true);
            } else if (cmd.startsWith("D")) {
                Map<String, Integer> ops = (Map<String, Integer>) properties.get("operations");
                if (cmd.contains("01")) ops.put("draw", ops.get("draw") + 1);
                if (cmd.contains("02")) ops.put("move", ops.get("move") + 1);
                if (cmd.contains("03")) ops.put("flash", ops.get("flash") + 1);
            } else if (cmd.startsWith("M02")) {
                properties.put("endOfFile", true);
            }
        }
    }

    private void printProperties() {
        System.out.println(properties.toString().replaceAll(", ", ",\n").replaceAll("=\\{", "={").replaceAll("}", "\n}"));
    }

    public void write(String filename) {
        try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
            String fs = (String) properties.get("formatSpec");
            writer.println("%FS" + (fs != null ? fs : "LAX35Y35") + "*");
            writer.println("%MO" + properties.get("units") + "*");
            writer.println("%IP" + properties.get("polarity") + "*");
            for (String ad : (List<String>) properties.get("apertureDefinitions")) {
                writer.println("%AD" + ad + "*");
            }
            Map<String, String> attrs = (Map<String, String>) properties.get("fileAttributes");
            for (Map.Entry<String, String> entry : attrs.entrySet()) {
                writer.println("%TF." + entry.getKey() + "," + entry.getValue() + "*");
            }
            writer.println("M02*");
        } catch (IOException e) {
            System.err.println("Write error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        GBRParser parser = new GBRParser("sample.gbr");
        parser.write("output.gbr");
    }
}

6. JavaScript Class for .GBR File Handling

This Node.js-compatible JavaScript class (using fs module) reads a .GBR file, parses/prints properties to console, and writes a reconstructed file. For browser, replace fs with FileReader.

const fs = require('fs');

class GBRParser {
  constructor(filename = null) {
    this.properties = {
      encoding: 'UTF-8 ASCII',
      structure: 'Command stream terminated by *',
      coordinateSystem: 'Absolute (default)',
      units: 'INCH (default)',
      formatSpec: null,
      polarity: 'POS (default)',
      apertureDefinitions: [],
      apertureList: [],
      stepRepeat: null,
      fileAttributes: {},
      apertureAttributes: {},
      interpolationModes: { linear: true, circularCW: false, circularCCW: false, multiQuadrant: true },
      operations: { draw: 0, move: 0, flash: 0 },
      endOfFile: false
    };
    if (filename) {
      this.read(filename);
    }
  }

  read(filename) {
    if (!fs.existsSync(filename)) {
      throw new Error(`${filename} not found`);
    }
    const content = fs.readFileSync(filename, 'utf8');
    this._parse(content);
    this._printProperties();
  }

  _parse(content) {
    const commands = content.split('*').map(cmd => cmd.trim()).filter(cmd => cmd);
    commands.forEach(cmd => {
      if (cmd.startsWith('%')) {
        let param = cmd.substring(1);
        if (param.startsWith('FS')) {
          this.properties.formatSpec = param.substring(2);
        } else if (param.startsWith('MO')) {
          this.properties.units = param.substring(2);
        } else if (param.startsWith('IP')) {
          this.properties.polarity = param.substring(2);
        } else if (param.startsWith('AD')) {
          this.properties.apertureDefinitions.push(param.substring(2));
        } else if (param.startsWith('AL')) {
          this.properties.apertureList = param.substring(2).split(',');
        } else if (param.startsWith('SR')) {
          this.properties.stepRepeat = param.substring(2);
        } else if (param.startsWith('TF.')) {
          const parts = param.substring(3).split(',');
          this.properties.fileAttributes[parts[0]] = parts.slice(1).join(',');
        } else if (param.startsWith('TA.')) {
          const parts = param.substring(3).split(',');
          this.properties.apertureAttributes[parts[0]] = parts.slice(1).join(',');
        }
      } else if (cmd.startsWith('G')) {
        if (cmd.includes('01')) this.properties.interpolationModes.linear = true;
        if (cmd.includes('02')) this.properties.interpolationModes.circularCW = true;
        if (cmd.includes('03')) this.properties.interpolationModes.circularCCW = true;
        if (cmd.includes('75')) this.properties.interpolationModes.multiQuadrant = true;
      } else if (cmd.startsWith('D')) {
        if (cmd.includes('01')) this.properties.operations.draw++;
        if (cmd.includes('02')) this.properties.operations.move++;
        if (cmd.includes('03')) this.properties.operations.flash++;
      } else if (cmd.startsWith('M02')) {
        this.properties.endOfFile = true;
      }
    });
  }

  _printProperties() {
    console.log(JSON.stringify(this.properties, null, 2));
  }

  write(filename) {
    let output = `%FS${this.properties.formatSpec || 'LAX35Y35'}*\n`;
    output += `%MO${this.properties.units}*\n`;
    output += `%IP${this.properties.polarity}*\n`;
    this.properties.apertureDefinitions.forEach(ad => {
      output += `%AD${ad}*\n`;
    });
    Object.entries(this.properties.fileAttributes).forEach(([key, val]) => {
      output += `%TF.${key},${val}*\n`;
    });
    output += 'M02*\n';
    fs.writeFileSync(filename, output);
  }
}

// Usage
// const parser = new GBRParser('sample.gbr');
// parser.write('output.gbr');
module.exports = GBRParser;

7. C Class (Struct with Functions) for .GBR File Handling

This C implementation uses stdio for file I/O, a struct for properties, parses/prints to stdout, and writes a reconstructed file. Compile with gcc gbr_parser.c -o gbr_parser.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char encoding[20];
    char structure[50];
    char coordinateSystem[30];
    char units[10];
    char formatSpec[20];
    char polarity[10];
    char** apertureDefinitions;
    int numApertures;
    char** apertureList;
    int numApertureList;
    char stepRepeat[50];
    // Simplified: only fileFunction for attributes
    char fileFunction[50];
    int linear, circularCW, circularCCW, multiQuadrant;
    int draw, move, flash;
    int endOfFile;
} GBRProperties;

GBRProperties* initProperties() {
    GBRProperties* props = malloc(sizeof(GBRProperties));
    strcpy(props->encoding, "UTF-8 ASCII");
    strcpy(props->structure, "Command stream terminated by *");
    strcpy(props->coordinateSystem, "Absolute (default)");
    strcpy(props->units, "INCH (default)");
    strcpy(props->formatSpec, "LAX35Y35");
    strcpy(props->polarity, "POS (default)");
    props->apertureDefinitions = NULL;
    props->numApertures = 0;
    props->apertureList = NULL;
    props->numApertureList = 0;
    strcpy(props->stepRepeat, "");
    strcpy(props->fileFunction, "");
    props->linear = 1;
    props->circularCW = 0;
    props->circularCCW = 0;
    props->multiQuadrant = 1;
    props->draw = 0;
    props->move = 0;
    props->flash = 0;
    props->endOfFile = 0;
    return props;
}

void parseGBR(GBRProperties* props, const char* content) {
    // Simple split simulation: find * and process substrings
    char* cmd = strtok((char*)content, "*");
    while (cmd != NULL) {
        cmd = strtok(NULL, "*"); // Skip first
        if (cmd) {
            while (*cmd == ' ' || *cmd == '\n' || *cmd == '\r') cmd++; // Trim
            if (strncmp(cmd, "%FS", 3) == 0) {
                strcpy(props->formatSpec, cmd + 3);
            } else if (strncmp(cmd, "%MO", 3) == 0) {
                strcpy(props->units, cmd + 3);
            } else if (strncmp(cmd, "%IP", 3) == 0) {
                strcpy(props->polarity, cmd + 3);
            } else if (strncmp(cmd, "%AD", 3) == 0) {
                props->numApertures++;
                props->apertureDefinitions = realloc(props->apertureDefinitions, props->numApertures * sizeof(char*));
                props->apertureDefinitions[props->numApertures - 1] = malloc(50);
                strcpy(props->apertureDefinitions[props->numApertures - 1], cmd + 3);
            } else if (strncmp(cmd, "G01", 3) == 0) props->linear = 1;
            else if (strncmp(cmd, "G02", 3) == 0) props->circularCW = 1;
            else if (strncmp(cmd, "G03", 3) == 0) props->circularCCW = 1;
            else if (strncmp(cmd, "G75", 3) == 0) props->multiQuadrant = 1;
            else if (strncmp(cmd, "D01", 3) == 0) props->draw++;
            else if (strncmp(cmd, "D02", 3) == 0) props->move++;
            else if (strncmp(cmd, "D03", 3) == 0) props->flash++;
            else if (strncmp(cmd, "M02", 3) == 0) props->endOfFile = 1;
            // Add more as needed
        }
        cmd = strtok(NULL, "*");
    }
}

void printProperties(GBRProperties* props) {
    printf("Encoding: %s\n", props->encoding);
    printf("Units: %s\n", props->units);
    printf("Format Spec: %s\n", props->formatSpec);
    printf("Polarity: %s\n", props->polarity);
    printf("Apertures: %d\n", props->numApertures);
    for (int i = 0; i < props->numApertures; i++) {
        printf("  %s\n", props->apertureDefinitions[i]);
    }
    printf("Operations - Draw: %d, Move: %d, Flash: %d\n", props->draw, props->move, props->flash);
    // Add more prints
}

void readGBR(GBRProperties* props, const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) {
        printf("File not found: %s\n", filename);
        return;
    }
    fseek(file, 0, SEEK_END);
    long size = ftell(file);
    fseek(file, 0, SEEK_SET);
    char* content = malloc(size + 1);
    fread(content, 1, size, file);
    content[size] = '\0';
    fclose(file);
    parseGBR(props, content);
    printProperties(props);
    free(content);
}

void writeGBR(GBRProperties* props, const char* filename) {
    FILE* file = fopen(filename, "w");
    if (!file) return;
    fprintf(file, "%%FS%s*\n", props->formatSpec);
    fprintf(file, "%%MO%s*\n", props->units);
    fprintf(file, "%%IP%s*\n", props->polarity);
    for (int i = 0; i < props->numApertures; i++) {
        fprintf(file, "%%AD%s*\n", props->apertureDefinitions[i]);
    }
    fprintf(file, "M02*\n");
    fclose(file);
}

void freeProperties(GBRProperties* props) {
    for (int i = 0; i < props->numApertures; i++) {
        free(props->apertureDefinitions[i]);
    }
    free(props->apertureDefinitions);
    free(props);
}

int main() {
    GBRProperties* props = initProperties();
    readGBR(props, "sample.gbr");
    writeGBR(props, "output.gbr");
    freeProperties(props);
    return 0;
}