Close

Importing a KiCAD SPECCTRA File

A project log for My Auto-Router

I am not that impressed with auto-routers. Why are they so bad?

agpcooperagp.cooper 07/24/2019 at 10:250 Comments

The KiCAD SPECCTRA File

KiCAD can export PCBs as "dsn" or SPECCTRA files. These file are ASCII and use Symbolic Expression format (often called  S-Expr format).  The SPECCTA specification is quite complex but for this application I only need the Board Area and the Pin locations.

I have modified code by https://github.com/benthepoet/c-sexpr-parser to read "dsn" format and then write it out again. As the rewritten "dsn" file was readable I assume that the code is correct.

Here is the modified "sexpr.h" and "sexpr.c" files:And finally my code to read/write the "dsn" file:

enum SNodeType {
  LIST,
  STRING,
  SYMBOL,
  INTEGER,
  FLOAT
};

struct SNode {
  struct SNode *next;
  enum SNodeType type;
  union {
    struct SNode *list;
    char *value;
  };
};

struct SNode *snode_parse(FILE *fp);
void snode_free(struct SNode *node);
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sexpr.h"
#define BUFFER_MAX 511

static char quoteChar='"';

int is_float(char *str) {
  char *ptr=NULL;
  strtod(str,&ptr);
  return !(*ptr);
}

int is_integer(char *str) {
  char *ptr=NULL;
  strtol(str,&ptr,10);
  return !(*ptr);
}

int is_lst_term(int c) {
  return ((c==EOF)||(isspace(c))||(c=='(')||(c ==')'));
}

int is_str_term(int c) {
  return ((c==EOF)||(c=='"'));
}

char *read_value(FILE *fp,int *c,int (*is_term)(int)) {
  int len=0;
  char buffer[BUFFER_MAX+1];

  while ((!is_term(*c=fgetc(fp)))&&(len<BUFFER_MAX)) {
    buffer[len]=(char)*c;
    len++;
  }
  buffer[len]='\0';
  char *str=malloc((unsigned int)(len+1)*sizeof(char));
  return strcpy(str,buffer);
}

// Recursively parse an s-expression from a file stream
struct SNode *snode_parse(FILE *fp) {
  // Using a linked list, nodes are appended to the list tail until we
  // reach a list terminator at which point we return the list head.
  struct SNode *tail=NULL,*head=NULL;
  int c;
  char lastSymbol[BUFFER_MAX+1]="";

  while ((c=fgetc(fp))!=EOF) {
    struct SNode *node=NULL;

    if (c==')') {
      // Terminate list recursion
      break;
    } else if (c=='(') {
      // Begin list recursion
      node=malloc(sizeof(struct SNode));
      node->type=LIST;
      node->list=snode_parse(fp);
    } else if ((strcmp(lastSymbol,"string_quote")==0)&&(!isspace(c))) {
      // Read symbol
      ungetc(c,fp);
      node=malloc(sizeof(struct SNode));
      node->value=read_value(fp,&c,&is_lst_term);
      // Put the terminator back
      ungetc(c,fp);
      node->type=SYMBOL;
      strcpy(lastSymbol,node->value);
      quoteChar=lastSymbol[0];
    } else if (c=='"') {
      node=malloc(sizeof(struct SNode));
      node->type=STRING;
      node->value=read_value(fp,&c,&is_str_term);
    } else if (!isspace(c)) {
      // Read a float, integer, or symbol
      ungetc(c,fp);
      node=malloc(sizeof(struct SNode));
      node->value=read_value(fp,&c,&is_lst_term);
      // Put the terminator back
      ungetc(c,fp);
      if (is_integer(node->value)) {
        node->type=INTEGER;
      } else if (is_float(node->value)) {
        node->type=FLOAT;
      } else {
        node->type=SYMBOL;
        strcpy(lastSymbol,node->value);
      }
    }

    if (node!=NULL) {
      // Terminate the node
      node->next=NULL;
      if (head==NULL) {
        // Initialize the list head
        head=tail=node;
      } else {
        // Append the node to the list tail
        tail=tail->next=node;
      }
    }
  }

  return head;
}

// Recursively free memory allocated by a node
void snode_free(struct SNode *node) {
  while (node!=NULL) {
    struct SNode *tmp=node;

    if (node->type==LIST) {
      snode_free(node->list);
    } else {
      // Free current value
      free(node->value);
      node->value=NULL;
    }
    node=node->next;
    // Free current node
    free(tmp);
    tmp=NULL;
  }
}

And my code to read the "dsn" file and then rewrite (export) it:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "sexpr.h"

void run_tests(struct SNode *node);

int main(int argc, char *argv[]) {
  // Unused parameters
  (void)argc;
  (void)argv;

  FILE *fp=fopen("DTL.dsn","r");
  struct SNode *node=snode_parse(fp);
  fclose(fp);

  run_tests(node);

  snode_free(node);

  return 0;
}

void run_tests(struct SNode *node) {
  int depth=1;
  int stackSize=1;

  struct SNode* current=NULL;
  struct SNode** stack=malloc((unsigned long)stackSize*sizeof(struct SNode));
  FILE *F1=fopen("test.dat","w");

  stack[0]=NULL;
  current=node->list;
  fprintf(F1,"( ");
  while (depth>0) {
    if (current==NULL) {
      depth--;
      fprintf(F1,") ");
      current=stack[depth];
    } else if (current->type==LIST) {
      stack[depth]=current->next;
      if (depth>=stackSize) {
        stackSize=depth+1;
        stack=(struct SNode**)realloc(stack,(unsigned int)stackSize*sizeof(struct SNode));
      }
      fprintf(F1,"\n");
      for (int i=0;i<depth;i++) fprintf(F1,"  ");
      fprintf(F1,"( ");
      depth++;
      for (int i=0;i<depth;i++) printf("  ");
      current=current->list;
    } else if (current->type==SYMBOL) {
      fprintf(F1,"%s ",current->value);
      current=current->next;
    } else if (current->type==STRING) {
      fprintf(F1,"\"%s\" ",current->value);
      current=current->next;
    } else if (current->type==INTEGER) {
      fprintf(F1,"%d ",atoi(current->value));
      current=current->next;
    } else if (current->type==FLOAT) {
      fprintf(F1,"%f ",atof(current->value));
      current=current->next;
    }
  }
  fclose(F1);
}

Here is the first few lines of the exported "dsn" file:

( pcb /home/alanx/KiCAD/DTL/DTL.dsn 
  ( parser 
    ( string_quote " ) 
    ( space_in_quoted_tokens on ) 
    ( host_cad "KiCad's Pcbnew" ) 
    ( host_version "4.0.7-e2-6376~61~ubuntu18.04.1" ) ) 
  ( resolution um 10 ) 
  ( unit um ) 
  ( structure 
    ( layer F.Cu 
      ( type signal ) 
      ( property 
        ( index 0 ) ) ) 
    ( layer B.Cu 
      ( type signal ) 
      ( property 
        ( index 1 ) ) ) 
    ( boundary 
      ( path pcb 0 130810 -95250 130810 -120650 175260 -120650 175260 -95250 130810 -95250 130810 -95250 ) ) 
    ( plane Earth 
      ( polygon F.Cu 0 129540 -93980 176530 -93980 176530 -121920 129540 -121920 ) ) 
    ( via "Via[0-1]_600:400_um" ) 
    ( rule 
      ( width 250 ) 
      ( clearance 200.100000 ) 
      ( clearance 200.100000 
        ( type default_smd ) ) 
      ( clearance 50 
        ( type smd_smd ) ) ) ) 

Now all I have to do is to export what I need.

Here is the scan of the "dsn" file for what I need:

PCB DTL
component Capacitors_THT:C_Rect_L7.0mm_W2.0mm_P5.00mm 
  place C1 154940 -105410 front 180 
component Diodes_THT:D_DO-35_SOD27_P7.62mm_Horizontal 
  place D1 165100 -110490 front 180 
  place D2 157480 -107950 front 0 
  place D3 139700 -107950 front 0 
  place D4 139700 -110490 front 0 
  place D5 139700 -113030 front 0 
component Socket_Strips:Socket_Strip_Straight_1x06_Pitch2.54mm 
  place J1 135890 -102870 front 0 
component TO_SOT_Packages_THT:TO-92_Molded_Narrow 
  place Q1 163830 -101600 front 90 
component Resistors_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal 
  place R1 147320 -102870 front 180 
  place R2 157480 -105410 front 0 
  place R3 147320 -105410 front 180 
image Capacitors_THT:C_Rect_L7.0mm_W2.0mm_P5.00mm 
  pin Round[A]Pad_1600_um 1 0 0 
  pin Round[A]Pad_1600_um 2 5000 0 
image Diodes_THT:D_DO-35_SOD27_P7.62mm_Horizontal 
  pin Rect[A]Pad_1600x1600_um 1 0 0 
  pin Oval[A]Pad_1600x1600_um 2 7620 0 
image Socket_Strips:Socket_Strip_Straight_1x06_Pitch2.54mm 
  pin Rect[A]Pad_1700x1700_um 1 0 0 
  pin Oval[A]Pad_1700x1700_um 2 0 -2540 
  pin Oval[A]Pad_1700x1700_um 3 0 -5080 
  pin Oval[A]Pad_1700x1700_um 4 0 -7620 
  pin Oval[A]Pad_1700x1700_um 5 0 -10160 
  pin Oval[A]Pad_1700x1700_um 6 0 -12700 
image TO_SOT_Packages_THT:TO-92_Molded_Narrow 
  pin Round[A]Pad_1000_um 2 1270 1270 
  pin Round[A]Pad_1000_um 3 2540 0 
  pin Rect[A]Pad_1000x1000_um 1 0 0 
image Resistors_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal 
  pin Round[A]Pad_1600_um 1 0 0 
  pin Oval[A]Pad_1600x1600_um 2 7620 0 
  net Net-(C1-Pad1) 
    pins C1-1 D2-1 Q1-2 R2-1 
  net Net-(C1-Pad2) 
    pins C1-2 D1-2 D3-2 D4-2 D5-2 R3-1 
  net Net-(D1-Pad1) 
    pins D1-1 D2-2 
  net Net-(D3-Pad1) 
    pins D3-1 J1-3 
  net Net-(D4-Pad1) 
    pins D4-1 J1-4 
  net Net-(D5-Pad1) 
    pins D5-1 J1-5 
  net Net-(J1-Pad1) 
    pins J1-1 R1-2 R3-2 
  net Net-(J1-Pad2) 
    pins J1-2 Q1-3 R1-1 
  net Earth 
    pins J1-6 Q1-1 R2-2 

Now I need to write code to link the "component/place" to the "image/pin" for positional information and  then decode "net/pins".

Update

I have bee a little bit distracted but back to it and have managed (though the use of associative arrays) to join the data:

C1 154940 -105410 front 180
  pin 1 0 0
  pin 2 5000 0
D1 165100 -110490 front 180
  pin 1 0 0
  pin 2 7620 0
D2 157480 -107950 front 0
  pin 1 0 0
  pin 2 7620 0
D3 139700 -107950 front 0
  pin 1 0 0
  pin 2 7620 0
D4 139700 -110490 front 0
  pin 1 0 0
  pin 2 7620 0
D5 139700 -113030 front 0
  pin 1 0 0
  pin 2 7620 0
J1 135890 -102870 front 0
  pin 1 0 0
  pin 2 0 -2540
  pin 3 0 -5080
  pin 4 0 -7620
  pin 5 0 -10160
  pin 6 0 -12700
Q1 163830 -101600 front 90
  pin 2 1270 1270
  pin 3 2540 0
  pin 1 0 0
R1 147320 -102870 front 180
  pin 1 0 0
  pin 2 7620 0
R2 157480 -105410 front 0
  pin 1 0 0
  pin 2 7620 0
R3 147320 -105410 front 180
  pin 1 0 0
  pin 2 7620 0

Still more work to do.

AlanX

Discussions