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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.