文章目录
- cadence SPB17.4 - export placement file to openpnp
- 概述
- 笔记
- openpnp支持的坐标文件格式
- openpnp导入坐标文件的内容规则
- 制作openpnp可用的坐标文件的思路列表
- 先自己写个程序试试, 这个不难, 也省心
- 程序写完了,好使
- 转换前的allego原版坐标文件
- 转换后的openpnp格式的坐标文件
- 被openpnp载入后的效果
- 发现的问题
- orcad导出料单
- 程序维护完成,好使
- 备注
- 补充 - 2023_0214_1733
- 有关于这3点的修正记录
- 坐标文件格式
- BOM列头必须是指定的列名, 而且列名的前后顺序必须一致
- 程序修正一下
- 补充 - 生成坐标前, 一定要将allegro原点设置到板子左下角
- 留言整理
- END
cadence SPB17.4 - export placement file to openpnp
概述
弄了一台openpnp, 设备整定好了.
现在需要导入板子的坐标文件.
遇到点问题: SPB17.4并不能导出openpnp可用的坐标文件.
尝试将SPB17.4做的板子, 弄出符合openpnp格式的坐标文件贴片用.
笔记
openpnp支持的坐标文件格式
试了一下, openpnp只支持AD(AD22)格式的坐标(csv)文件.
这种坐标(csv)文件, 在openpnp中叫做 Named csv
这种文件的格式有点特别:
- 每一项的内容,必须用"符号包裹
试了一下立创EDA和cadence导出的csv的每项内容不是用"符号包裹的.
用AD22导出的坐标csv文件例子如下:
Altium Designer Pick and Place Locations
Z:\test\Pick Place for NEW_PCB_2022-11-12.csv
========================================================================================================================
File Design Information:
Date: 12/11/22
Time: 13:42
Revision: Not in VersionControl
Variant: No variations
Units used: mm
"Designator","Comment","Layer","Footprint","Center-X(mm)","Center-Y(mm)","Rotation","Description","Ref-X(mm)","Ref-Y(mm)","Pad-X(mm)","Pad-Y(mm)"
"U1","BAT-SMD_CR1220-2","TopLayer","BAT-SMD_CR1220-2","58.2270mm","68.2269mm","0","","58.2270mm","68.2269mm","50.2270mm","68.2269mm"
用allegro导出的新版坐标文件并没有标题, 都是数据,用空格分开, 而且单位不可选
如下:
UUNITS = MILS
J4 216.89 950.18 270 MICRO-USB-SMD_MICROXNJ
TP1 5809.00 3234.13 0 TEST_TH_PIN_1D0MM
TP10 4051.00 2358.00 0 TEST_TH_PIN_1D0MM
TP11 2670.00 3570.00 0 TEST_TH_PIN_1D0MM
用allegro导出的旧版坐标文件有标题, 但是分隔符是’!‘,不是’,'符号, 而且单位不可选
如下:
VERSION = 2.0
UUNITS = MILS
# refdes ! symbol_x ! symbol_y ! rotation ! mirror ! symbol_name ! embedded_layer
#-----------------------------------------------------------------------------------------------------------------------
J4 ! 216.89 ! 950.18 ! 270 ! ! MICRO-USB-SMD_MICROXNJ !
TP1 ! 5809.00 ! 3234.13 ! 0 ! ! TEST_TH_PIN_1D0MM !
TP10 ! 4051.00 ! 2358.00 ! 0 ! ! TEST_TH_PIN_1D0MM !
TP11 ! 2670.00 ! 3570.00 ! 0 ! ! TEST_TH_PIN_1D0MM !
TP12 ! 2820.00 ! 3569.00 ! 0 ! ! TEST_TH_PIN_1D0MM !
TP2 ! 5810.00 ! 3080.00 ! 0 ! ! TEST_TH_PIN_1D0MM !
JLC导出的坐标文件如下, 有题头, 但是分隔符是空格, openpnp不认识
Designator Footprint Mid X Mid Y Ref X Ref Y Pad X Pad Y Layer Rotation Comment
"U1" "BAT-SMD_CR1220-2" "20mm" "-10mm" "20mm" "-10mm" "12mm" "-10mm" "T" "0" "BAT-SMD_CR1220-2"
那到底openpnp要求的坐标文件是啥格式的呢? 这自己乱实验,不好弄啊.
找到了openpnp官方文档
openpnp导入坐标文件的内容规则
Importing Centroid Data
关于openpnp导入Named CSV的规则描述如下:
Others
OpenPnP includes a Named CSV importer (File -> Import -> Named CSV) which can import many types of CSV files. Coordinate data must be in Millimeters. Field separator must be a comma.
坐标数据单位必须是mm, 分隔符必须是逗号
Format specifications need to be inside the first 10 lines of file.
格式说明必须在文件的头10行之内
The first six fields are required in order to successfully import centroid data.
前6个数据(元件位号, 元件值, 元件封装, 元件x坐标, 元件y坐标, 元件旋转角度)必须有, 是为了引入元件坐标数据必须有的字段.
The fields that the Named CSV importer will look for are: (not case sensitive)
字段名称如下,忽略大小写
Refs: “Designator”, “Part”, “Component”, “RefDes”, “Ref”
元件位号
Vals: “Value”, “Val”, “Comment”, “Comp_Value”
元件值
Packs: “Footprint”, “Package”, “Pattern”, “Comp_Package”
元件封装
Xs: “X”, “X (mm)”, “Ref X”, “PosX”, “Ref-X(mm)”, “Ref-X(mil)”, “Sym_X”
元件X坐标
Ys: “Y”, “Y (mm)”, “Ref Y”, “PosY”, “Ref-Y(mm)”, “Ref-Y(mil)”, “Sym_Y”
元件Y坐标
Rots: “Rotation”, “Rot”, “Rotate”, “Sym_Rotate”
元件旋转角度
TBs: “Layer”, “Side”, “TB”, “Sym_Mirror”
元件所在层(可选)
Heights: “Height”, “Height(mil)”, “Height(mm)”
元件高度
Example:
"Designator","Footprint","Mid X","Mid Y","Ref X","Ref Y","Pad X","Pad Y","Layer","Rotation","Comment"
"C1","NICHICON_A","58.674mm","7.2263mm","58.674mm","7.239mm","58.674mm","8.7376mm","T","90.00","10uF"
"C3","CAP0603","54.102mm","8.255mm","54.102mm","8.255mm","54.102mm","9.1694mm","T","270.00","1uF"
发现官方给的例子, 没有多余的空格
分析一下openpnp官方给的例子
元件位号 Designator
元件封装 Footprint
元件X坐标 Ref X
元件Y坐标 Ref Y
元件所在层 Layer => 不是官方规定的6个必须数据, 如果要贴双面, 就需要. 不过openpnp默认是贴一面的. 如果要贴另外一面, 就是另外一个工程.
元件旋转角度 Rotation
元件值 Comment
“Mid X”,“Mid Y”, “Pad X”,“Pad Y” 这4个都是非必须的附加数据.
用官方给的例子作为Named csv, 让openpnp导入试试.
用openpnp载入了官方例子做的openpnp_example.txt, 好使, 定义的2个元件都载入了.
制作openpnp可用的坐标文件的思路列表
-
allegro正常导出坐标文件place_txt.txt, 按照一种固定格式导出(元件的坐标原点是谁? + 新格式/旧格式?), 写一个小程序, 将坐标文件转为openpnp可识别的坐标文件格式. 这个不难, 工作量也不大.
-
能不能找个第三方软件(AD, CAM350), 将allegro的数据(转成第三方格式的PCB文件, 或者就是导入完整gerber)导出为openpnp可识别的坐标文件格式. 这个不一定行(哪有那么现成的软件?), 要实验才行.
先自己写个程序试试, 这个不难, 也省心
allegro导出的坐标文件的选项如下
元件的符号原点 + 新格式
allegro生成的原始坐标文件样例如下:
UUNITS = MILS
J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
TP1 5809.00 3234.13 0 TEST_TH_PIN_1D0MM
TP10 4051.00 2358.00 0 TEST_TH_PIN_1D0MM
TP11 2670.00 3570.00 0 TEST_TH_PIN_1D0MM
TP12 2820.00 3569.00 0 TEST_TH_PIN_1D0MM
TP2 5810.00 3080.00 0 TEST_TH_PIN_1D0MM
TP3 2020.00 2857.00 0 TEST_TH_PIN_1D0MM
第1项是位号
第2项是X坐标
第3项是Y坐标
第4项时旋转角度
第5项是封装
以上各项跟openpnp要求的6项比对, 可知, 少了元件值. 拿位号和封装拼一个元件值吧
就拿这个格式的素材写坐标文件转换程序吧.
程序写完了,好使
程序写完了,不到400行,验证好使,可以被openpnp正常载入
// @file openpnp_named_cvs_convert.cpp
// @brief 转换SPB17.4 allegro导出的坐标文件(新格式)为openpnp可导入的坐标文件
// @note 编译环境 vs2022 vc++ console
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
void process_allegro_csv(const char* pFile, const char* pFileNew);
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew);
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil);
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6);
double mil2mm(double fMil, bool bIsMil);
int main(int argc, char** argv)
{
char* pFile = NULL;
char sz_new_file[256];
do {
if (2 != argc)
{
// 测试用的 allegro导出的原始坐标文件为 allegro_org.txt
printf("请给出要转换的csv文件名称\r\n");
printf("usage : openpnp_named_cvs_convert x.csv\r\n");
break;
}
if (NULL == argv[1])
{
printf("err : 给定的文件名称为空\r\n");
break;
}
pFile = argv[1];
printf("要处理的文件为 : %s\r\n", pFile);
memset(sz_new_file, 0, sizeof(sz_new_file));
sprintf(sz_new_file, "%s.csv", pFile);
printf("转换完的文件为 : %s\r\n", sz_new_file);
process_allegro_csv(pFile, sz_new_file);
printf("处理结束 : 请查看 %s 是否处理正确\r\n", sz_new_file);
} while (0);
return EXIT_SUCCESS;
}
void process_allegro_csv(const char* pFile, const char* pFileNew)
{
FILE* fpFile = NULL;
do {
// 在编译选项(预处理器)中加入 _CRT_SECURE_NO_WARNINGS 宏, 防止编译警告
fpFile = fopen(pFile, "r");
if (NULL == fpFile)
{
printf("err : 文件(%s)打开失败\r\n", pFile);
break;
}
process_allegro_csv(pFile, fpFile, pFileNew);
} while (0);
if (NULL != fpFile)
{
fclose(fpFile);
fpFile = NULL;
}
}
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew)
{
char sz_buf_rd[1024];
size_t nRdCnt = 0;
char* pFind = NULL;
char sz_buf_field1[0x100];
char sz_buf_field2[0x100];
char sz_buf_field3[0x100];
char sz_buf_field4[0x100];
char sz_buf_field5[0x100];
char sz_buf_field6[0x100];
int iCnt = 0;
bool bUnitFind = false;
bool bUnitIsMm = false;
bool bUnitIsMil = false;
FILE* fpFileNew = NULL;
do {
if (NULL == pFileNew)
{
break;
}
if (NULL == fpFileNew)
{
fpFileNew = fopen(pFileNew, "w");
if (NULL == fpFileNew)
{
printf("建立新文件(%s)失败\r\n", pFileNew);
break;
}
}
memset(sz_buf_rd, 0, sizeof(sz_buf_rd));
pFind = fgets(sz_buf_rd, sizeof(sz_buf_rd), fpFile);
if (NULL == pFind)
{
printf("原始文件读取完毕\r\n");
break;
}
if (!bUnitFind)
{
// 查找 UUNITS = MILS
pFind = strstr(sz_buf_rd, "UUNITS = ");
if (NULL == pFind)
{
continue;
}
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
iCnt = sscanf(sz_buf_rd, "UUNITS = %255s", sz_buf_field1); // MILS
if (iCnt <= 0)
{
continue;
}
if (0 == strcmp(sz_buf_field1, "MILS"))
{
// 单位是mil
bUnitFind = true;
bUnitIsMm = false;
bUnitIsMil = true;
}
else if (0 == strcmp(sz_buf_field1, "MM"))
{
// 单位是mm
bUnitFind = true;
bUnitIsMm = true;
bUnitIsMil = false;
}
else {
printf("err : 文件格式不对, 单位应该是mil和mm其中一个\r\n");
break;
}
// 写处理完的csv的注释部分
process_convert_head(pFile, fpFile, pFileNew, fpFileNew, bUnitFind, bUnitIsMm, bUnitIsMil);
}
else {
// 查找 J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
//
// 如果有背面元件时, 就有6列, 会在第5列多出一个镜像的m字符, 第6列是封装
// 如果是正面的元件, 就只有5列
// LOGO2 3780.00 1220.00 0 m LOGO_KRGY
// LOGO1 1467.42 514.83 0 LOGO_STC15_BOX4
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
memset(sz_buf_field2, 0, sizeof(sz_buf_field2));
memset(sz_buf_field3, 0, sizeof(sz_buf_field3));
memset(sz_buf_field4, 0, sizeof(sz_buf_field4));
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
memset(sz_buf_field6, 0, sizeof(sz_buf_field6));
iCnt = sscanf(sz_buf_rd, "%s %s %s %s %s %s",
sz_buf_field1,
sz_buf_field2,
sz_buf_field3,
sz_buf_field4,
sz_buf_field5,
sz_buf_field6); // MILS
if ((iCnt < 5) && (iCnt > 6))
{
continue;
}
if (5 == iCnt)
{
// 是正面元件, 第5列为空, 只有第6列为封装
strcpy(sz_buf_field6, sz_buf_field5);
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
}
else if (6 == iCnt) {
// 是反面面元件, 第5列为m, 第6列为封装
}
process_convert_body(fpFileNew, bUnitIsMm, sz_buf_field1, sz_buf_field2, sz_buf_field3, sz_buf_field4, sz_buf_field5, sz_buf_field6);
}
} while (1);
if (NULL != fpFileNew)
{
fclose(fpFileNew);
fpFileNew = NULL;
}
}
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil)
{
// 在文件的前10行(只能是前10行, openpnp有规定)写入一些注释(e.g. 我是谁? 我在哪?)
/*
Altium Designer Pick and Place Locations
Z:\test\Pick Place for NEW_PCB_2022-11-12.csv
========================================================================================================================
File Design Information:
Date: 12/11/22
Time: 13:42
Revision: Not in VersionControl
Variant: No variations
Units used: mm
*/
char sz_buf[1024];
do {
if ((NULL == fpFile) || (NULL == fpFile) || (!bUnitFind))
{
break;
}
// bool bUnitIsMm, bool bUnitIsMil
if (!bUnitIsMm && !bUnitIsMil)
{
// mm 和 mil 单位必须有一个为true
break;
}
// line 1
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "openpnp Named csv Pick and Place Locations\n"); // 以w方式打开的文件, 换行的话,\n就行了
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 2
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "org file = %s\n", pFile);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 3
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "convert file = %s\n", pFileNew);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 4
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "========================================================================================================================\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 5
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "File Design Information:\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 6
memset(sz_buf, 0, sizeof(sz_buf));
// Units used: mm
sprintf(sz_buf, "Units used: mm\n"); // openpnp用的坐标文件, 必须是mm
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 7
// J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Comment"
// "Designator","Comment","Footprint","Rotation","Ref-X(mm)","Ref-Y(mm)" // 这个是用AD出的坐标文件整理后的可用题头
// "Designator", "Comment", "Layer", "Footprint", "Center-X(mm)", "Center-Y(mm)", "Rotation", "Description", "Ref-X(mm)", "Ref-Y(mm)", "Pad-X(mm)", "Pad-Y(mm)"
// "U1", "BAT-SMD_CR1220-2", "TopLayer", "BAT-SMD_CR1220-2", "58.2270mm", "68.2269mm", "0", "", "58.2270mm", "68.2269mm", "50.2270mm", "68.2269mm"
// 发现allegro导出的坐标文件, 是分正反面的, 所以要加上Layer字段
memset(sz_buf, 0, sizeof(sz_buf));
// 前6列是allegro导出的坐标文件就有的, 第7列是拼装的(位号 + 封装), 或留空(因为allegro导出的坐标文件,本来就没有元件值)
// 列1 列2 列3 列4 列5 列6 列7
sprintf(sz_buf, "\"Designator\",\"Ref-X(mm)\",\"Ref-Y(mm)\",\"Rotation\",\"Layer\", \"Footprint\",\"Comment\"\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
} while (0);
}
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6)
{
// 原始
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Layer","Comment"
// J4 157.00 899.00 270 m MICRO-USB-SMD_MICROXNJ
// 转换之后
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "BottomLayer", "10uF"
// "C2", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
char sz_buf_field5_new[0x100];
char sz_buf_field7_new[0x100];
char sz_buf_write[1024];
double fTmp = 0;
double fX = 0;
double fY = 0;
double fAngle = 0;
// sz_buf_field1 is J4
// sz_buf_field2 is 157.00
fTmp = atof(sz_buf_field2);
fX = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field3 is 899.00
fTmp = atof(sz_buf_field3);
fY = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field4 is 270
fAngle = atof(sz_buf_field4);
// sz_buf_field5 is m // 正反面, 反面为m 正面为空
memset(sz_buf_field5_new, 0, sizeof(sz_buf_field5_new));
strcpy(sz_buf_field5_new, (0 == strcmp(sz_buf_field5, "m")) ? "BottomLayer" : "TopLayer");
// sz_buf_field6 is MICRO-USB-SMD_MICROXNJ
// sz_buf_field7_new is value (Designator + Comment)
memset(sz_buf_field7_new, 0, sizeof(sz_buf_field7_new));
// sprintf(sz_buf_field6_new, "%s_%s", sz_buf_field1, sz_buf_field5);
// 坐标文件本来就没有值, 留空吧, 看看能行不?
// 拼新行写入新文件
memset(sz_buf_write, 0, sizeof(sz_buf_write));
// BottomLayer
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
sprintf(sz_buf_write, "\"%s\", \"%.3fmm\", \"%.3fmm\", \"%.2f\", \"%s\", \"%s\", \"%s\"\n",
sz_buf_field1,
fX,
fY,
fAngle,
sz_buf_field5_new,
sz_buf_field6,
sz_buf_field7_new
);
fwrite(sz_buf_write, sizeof(char), strlen(sz_buf_write), fpFileNew);
}
double mil2mm(double fMil, bool bIsMil)
{
double fMm = fMil;
if (bIsMil)
{
// 100mil = 2.54mm
//
// 100mil / 2.54 = 2.54/2.54mm
// 1mm = 100mil / 2.54
//
// 1mil = 2.54mm / 100
//
fMm = fMil * 2.54 / 100;
}
return fMm;
}
转换前的allego原版坐标文件
元件是有正反面的, 第5列是正反面。
如果是正面的元件,第5列为空,第6列为封装
如果是反面的元件,第5列为m(代表元件被镜像了),第6列为封装
UUNITS = MILS
J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
TP1 5809.00 3234.13 0 TEST_TH_PIN_1D0MM
TP10 4051.00 2358.00 0 TEST_TH_PIN_1D0MM
TP11 2670.00 3570.00 0 TEST_TH_PIN_1D0MM
TP12 2820.00 3569.00 0 TEST_TH_PIN_1D0MM
TP2 5810.00 3080.00 0 TEST_TH_PIN_1D0MM
TP3 2020.00 2857.00 0 TEST_TH_PIN_1D0MM
TP4 1870.00 2860.00 0 TEST_TH_PIN_1D0MM
TP5 1870.00 1445.35 0 TEST_TH_PIN_1D0MM
TP6 2015.00 1448.00 0 TEST_TH_PIN_1D0MM
TP7 3286.59 200.00 0 TEST_TH_PIN_1D0MM
TP8 3430.00 200.00 0 TEST_TH_PIN_1D0MM
TP9 3904.00 2357.00 0 TEST_TH_PIN_1D0MM
M1 3807.00 3358.00 180 STC15_EXP_BOX4_MCU_BOARD_FP_V1
LOGO2 3780.00 1220.00 0 m LOGO_KRGY
LOGO1 1467.42 514.83 0 LOGO_STC15_BOX4
DW2 139.00 611.00 0 DW_HOLE_D1R0MM_D2R0MM
转换后的openpnp格式的坐标文件
openpnp Named csv Pick and Place Locations
org file = allegro_org.txt
convert file = allegro_org.txt.csv
========================================================================================================================
File Design Information:
Units used: mm
"Designator","Ref-X(mm)","Ref-Y(mm)","Rotation","Layer", "Footprint","Comment"
"J4", "3.988mm", "22.835mm", "270.00", "TopLayer", "MICRO-USB-SMD_MICROXNJ", ""
"TP1", "147.549mm", "82.147mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP10", "102.895mm", "59.893mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP11", "67.818mm", "90.678mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP12", "71.628mm", "90.653mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP2", "147.574mm", "78.232mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP3", "51.308mm", "72.568mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP4", "47.498mm", "72.644mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP5", "47.498mm", "36.712mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP6", "51.181mm", "36.779mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP7", "83.479mm", "5.080mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP8", "87.122mm", "5.080mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"TP9", "99.162mm", "59.868mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", ""
"M1", "96.698mm", "85.293mm", "180.00", "TopLayer", "STC15_EXP_BOX4_MCU_BOARD_FP_V1", ""
"LOGO2", "96.012mm", "30.988mm", "0.00", "BottomLayer", "LOGO_KRGY", ""
"LOGO1", "37.272mm", "13.077mm", "0.00", "TopLayer", "LOGO_STC15_BOX4", ""
被openpnp载入后的效果
买的openpnp设备只支持一面贴片, 反面的元件被滤掉了。
只有一个LOGO2在反面, 载入后找不到LOGO2了。
发现的问题
AD导出的坐标文件有Comment, 连上封装,就可以确定一个唯一的料,e.g. LED_红色 或者 C0603_20pf
但是allegro导出的坐标文件没有Comment, 就是说,只是坐标文件(哪个位号摆在哪个位置,角度是多少),转换完了,我只能将Comment位置添为空, 因为也确实无法从allegro导出的坐标文件中得到一个位号是个啥元件…
这样就导致, 这个转换完的坐标文件, 无法正常贴片, 料栈上都是针对具体料的,每个元件再指定料栈.
等于说, 这么转换是废的,必须结合其他allegro文件, e.g. 料单来填写Comment的内容。
去ORCAD中出了一次料单,导出料单时, 多加了一个Part_Number的列。导出后, 可以另存为用’,'分隔的csv文件
如下:
Item Number,Quantity,Value,Description,Part Reference,Part_Number
1,1,BAT_CR1220-2,电池底座 适用电池:CR1220,BAT1,BAT_0001
2,3,47uF/20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
3,17,100nF/10%/50V/0603,贴片电容(MLCC) 100nF ±10% 50V 0603,C2 C4 C5 C6 C9 C10 C11 C12 C13 C14 C19 C21 C22 C23 C24 C27 C28,CAP_0003
4,2,7pF/0.25pf/50V/0603,贴片电容(MLCC) 7pF ±0.25pF 50V 0603,C8 C32,CAP_0009
5,3,100uF/20%/50V/SMD/8x10mm,贴片型铝电解电容 100uF ±20% 50V SMD 8x10mm,C15 C20 C31,CAP_0002
6,1,10nF/10%/25V/0603,贴片电容(MLCC) 10nF ±10% 25V 0603,C16,CAP_0004
7,2,20pF/5%/50V/0603,贴片电容(MLCC) 20pF ±5% 50V 0603,C17 C18,CAP_0008
8,3,10uF/10%/25V/0805,贴片电容(MLCC) 10uF ±10% 25V 0805,C25 C26 C29,CAP_0006
我用了CIS库,每个Part_Number就代表唯一私有料号。
我还必须在上面程序的基础上,再从料单csv中,将坐标文件中每个元件的Part_Number和Vaule找出来, 拼好,填入openpnp用的csv的Comment字段中。
看来必须要这样才行(使openpnp导入坐标文件后可以区分中每一种料对应的多个元件)。
明天再弄吧,到处都是坑。
orcad导出料单
执行后, 会用WPS打开生成好的料单.
另存为一种CSV格式文件到本地供分析.
现在, 将BOM文件中的Value, Part_Number提取出来, 填充到openpnp坐标文件对应元件的Comment中.
就在昨天的工程上升级一下就好.
程序维护完成,好使
改完的程序不到800行, 如下
// @file openpnp_named_cvs_convert.cpp
// @brief 转换SPB17.4 allegro导出的坐标文件(新格式) + orcad BOM单 转换为openpnp可导入的坐标文件
// @note 编译环境 vs2022 vc++ console
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <list>
#include <string>
#define OPENPNP_NAMED_CSV_FILE "openpnp_named_csv.csv"
// 2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
typedef struct _tag_bom_item {
char sz_ItemNumber[0x100];
int i_Quantity;
char sz_Value[0x100];
char sz_Value_BOM[0x100];
char sz_Description[0x100];
char sz_Part_Number[0x100];
// Part Reference
std::list<std::string> list_Part;
}TAG_BOM_ITEM;
std::list<TAG_BOM_ITEM*> g_list_bom_item; // 将BOM文件载入到这个list供查询
void clear_bom_list(void);
void replace_char(char* pszSrc, char c_find, char c_new);
void replace_char_between_double_quotation_marks(char* pszSrc, char c_old, char c_new);
void get_bom_part_sn(std::list<TAG_BOM_ITEM*>& list, char* psz_part_id, char** ppsz_field);
void process_allegro_csv(const char* pszAllegroFile, const char* pszOrcadBomFile, const char* pszOpenpnpCsvFile);
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew);
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil);
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6);
double mil2mm(double fMil, bool bIsMil);
bool process_orcad_bom_file(const char* pszOrcadBomFile);
int main(int argc, char** argv)
{
char* pszAllegroFile = NULL; // allegro 坐标文件
char* pszOrcadBomFile = NULL; // orcad 料单
char sz_openpnp_file[256]; // 转换完成后的openpnp坐标文件
do {
if (3 != argc)
{
// 测试用的 allegro导出的原始坐标文件为 allegro_org.txt
printf("请给出要转换的csv文件名称\r\n");
printf("usage : openpnp_named_cvs_convert allegro_org.txt BOM_orcad_org.csv\r\n");
printf("allegro_org.txt 是allegro导出的坐标文件\r\n");
printf("BOM_orcad_org.csv 是orcad导出的料单\r\n");
printf("%s 是转换后的openpnp可导入的最终坐标文件\r\n", OPENPNP_NAMED_CSV_FILE);
break;
}
if (NULL == argv[1])
{
printf("err : 给定的Allegro坐标文件名称为空\r\n");
break;
}
pszAllegroFile = argv[1];
printf("要处理的Allegro坐标文件为 : %s\r\n", pszAllegroFile);
if (NULL == argv[2])
{
printf("err : 给定的Orcad料单文件名称为空\r\n");
break;
}
pszOrcadBomFile = argv[2];
printf("要处理的Orcad料单文件为 : %s\r\n", pszOrcadBomFile);
memset(sz_openpnp_file, 0, sizeof(sz_openpnp_file));
sprintf(sz_openpnp_file, "%s", OPENPNP_NAMED_CSV_FILE);
printf("预期转换完的openpnp坐标文件为 : %s\r\n", sz_openpnp_file);
process_allegro_csv(pszAllegroFile, pszOrcadBomFile, sz_openpnp_file);
printf("处理结束 : 请查看转换完的openpnp坐标文件 %s 是否处理正确\r\n", sz_openpnp_file);
} while (0);
clear_bom_list();
return EXIT_SUCCESS;
}
void process_allegro_csv(const char* pszAllegroFile, const char* pszOrcadBomFile, const char* pszOpenpnpCsvFile)
{
FILE* fpAllegroFile = NULL;
do {
// 在编译选项(预处理器)中加入 _CRT_SECURE_NO_WARNINGS 宏, 防止编译警告
fpAllegroFile = fopen(pszAllegroFile, "r");
if (NULL == fpAllegroFile)
{
printf("err : Allegro坐标文件(%s)打开失败\r\n", pszAllegroFile);
break;
}
if (!process_orcad_bom_file(pszOrcadBomFile))
{
printf("err : 处理orcad BOM文件(%s)失败\r\n", pszOrcadBomFile);
break;
}
process_allegro_csv(pszAllegroFile, fpAllegroFile, pszOpenpnpCsvFile);
} while (0);
if (NULL != fpAllegroFile)
{
fclose(fpAllegroFile);
fpAllegroFile = NULL;
}
}
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew)
{
char sz_buf_rd[1024];
size_t nRdCnt = 0;
char* pFind = NULL;
char sz_buf_field1[0x100];
char sz_buf_field2[0x100];
char sz_buf_field3[0x100];
char sz_buf_field4[0x100];
char sz_buf_field5[0x100];
char sz_buf_field6[0x100];
int iCnt = 0;
bool bUnitFind = false;
bool bUnitIsMm = false;
bool bUnitIsMil = false;
FILE* fpFileNew = NULL;
do {
if (NULL == pFileNew)
{
break;
}
if (NULL == fpFileNew)
{
fpFileNew = fopen(pFileNew, "w");
if (NULL == fpFileNew)
{
printf("建立新文件(%s)失败\r\n", pFileNew);
break;
}
}
memset(sz_buf_rd, 0, sizeof(sz_buf_rd));
pFind = fgets(sz_buf_rd, sizeof(sz_buf_rd), fpFile);
if (NULL == pFind)
{
printf("alegro坐标文件 : 读取完毕\r\n");
break;
}
if (!bUnitFind)
{
// 查找 UUNITS = MILS
pFind = strstr(sz_buf_rd, "UUNITS = ");
if (NULL == pFind)
{
continue;
}
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
iCnt = sscanf(sz_buf_rd, "UUNITS = %255s", sz_buf_field1); // MILS
if (iCnt <= 0)
{
continue;
}
if (0 == strcmp(sz_buf_field1, "MILS"))
{
// 单位是mil
bUnitFind = true;
bUnitIsMm = false;
bUnitIsMil = true;
}
else if (0 == strcmp(sz_buf_field1, "MM"))
{
// 单位是mm
bUnitFind = true;
bUnitIsMm = true;
bUnitIsMil = false;
}
else {
printf("err : 文件格式不对, 单位应该是mil和mm其中一个\r\n");
break;
}
// 写处理完的csv的注释部分
process_convert_head(pFile, fpFile, pFileNew, fpFileNew, bUnitFind, bUnitIsMm, bUnitIsMil);
}
else {
// 查找 J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
//
// 如果有背面元件时, 就有6列, 会在第5列多出一个镜像的m字符, 第6列是封装
// 如果是正面的元件, 就只有5列
// LOGO2 3780.00 1220.00 0 m LOGO_KRGY
// LOGO1 1467.42 514.83 0 LOGO_STC15_BOX4
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
memset(sz_buf_field2, 0, sizeof(sz_buf_field2));
memset(sz_buf_field3, 0, sizeof(sz_buf_field3));
memset(sz_buf_field4, 0, sizeof(sz_buf_field4));
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
memset(sz_buf_field6, 0, sizeof(sz_buf_field6));
iCnt = sscanf(sz_buf_rd, "%s %s %s %s %s %s",
sz_buf_field1,
sz_buf_field2,
sz_buf_field3,
sz_buf_field4,
sz_buf_field5,
sz_buf_field6); // MILS
if ((iCnt < 5) && (iCnt > 6))
{
continue;
}
if (5 == iCnt)
{
// 是正面元件, 第5列为空, 只有第6列为封装
strcpy(sz_buf_field6, sz_buf_field5);
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
}
else if (6 == iCnt) {
// 是反面面元件, 第5列为m, 第6列为封装
}
process_convert_body(fpFileNew, bUnitIsMm, sz_buf_field1, sz_buf_field2, sz_buf_field3, sz_buf_field4, sz_buf_field5, sz_buf_field6);
}
} while (1);
if (NULL != fpFileNew)
{
fclose(fpFileNew);
fpFileNew = NULL;
}
}
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil)
{
// 在文件的前10行(只能是前10行, openpnp有规定)写入一些注释(e.g. 我是谁? 我在哪?)
/*
Altium Designer Pick and Place Locations
Z:\test\Pick Place for NEW_PCB_2022-11-12.csv
========================================================================================================================
File Design Information:
Date: 12/11/22
Time: 13:42
Revision: Not in VersionControl
Variant: No variations
Units used: mm
*/
char sz_buf[1024];
do {
if ((NULL == fpFile) || (NULL == fpFile) || (!bUnitFind))
{
break;
}
// bool bUnitIsMm, bool bUnitIsMil
if (!bUnitIsMm && !bUnitIsMil)
{
// mm 和 mil 单位必须有一个为true
break;
}
// line 1
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "openpnp Named csv Pick and Place Locations\n"); // 以w方式打开的文件, 换行的话,\n就行了
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 2
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "org file = %s\n", pFile);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 3
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "convert file = %s\n", pFileNew);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 4
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "========================================================================================================================\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 5
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "File Design Information:\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 6
memset(sz_buf, 0, sizeof(sz_buf));
// Units used: mm
sprintf(sz_buf, "Units used: mm\n"); // openpnp用的坐标文件, 必须是mm
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 7
// J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Comment"
// "Designator","Comment","Footprint","Rotation","Ref-X(mm)","Ref-Y(mm)" // 这个是用AD出的坐标文件整理后的可用题头
// "Designator", "Comment", "Layer", "Footprint", "Center-X(mm)", "Center-Y(mm)", "Rotation", "Description", "Ref-X(mm)", "Ref-Y(mm)", "Pad-X(mm)", "Pad-Y(mm)"
// "U1", "BAT-SMD_CR1220-2", "TopLayer", "BAT-SMD_CR1220-2", "58.2270mm", "68.2269mm", "0", "", "58.2270mm", "68.2269mm", "50.2270mm", "68.2269mm"
// 发现allegro导出的坐标文件, 是分正反面的, 所以要加上Layer字段
memset(sz_buf, 0, sizeof(sz_buf));
// 前6列是allegro导出的坐标文件就有的, 第7列是拼装的(位号 + 封装), 或留空(因为allegro导出的坐标文件,本来就没有元件值)
// 列1 列2 列3 列4 列5 列6 列7
sprintf(sz_buf, "\"Designator\",\"Ref-X(mm)\",\"Ref-Y(mm)\",\"Rotation\",\"Layer\", \"Footprint\",\"Comment\"\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
} while (0);
}
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6)
{
// 原始
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Layer","Comment"
// J4 157.00 899.00 270 m MICRO-USB-SMD_MICROXNJ
// 转换之后
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "BottomLayer", "10uF"
// "C2", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
char sz_buf_field5_new[0x100];
char sz_buf_field7_new[0x100];
char sz_buf_write[1024];
double fTmp = 0;
double fX = 0;
double fY = 0;
double fAngle = 0;
char* psz_tmp = NULL;
// sz_buf_field1 is J4
// sz_buf_field2 is 157.00
fTmp = atof(sz_buf_field2);
fX = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field3 is 899.00
fTmp = atof(sz_buf_field3);
fY = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field4 is 270
fAngle = atof(sz_buf_field4);
// sz_buf_field5 is m // 正反面, 反面为m 正面为空
memset(sz_buf_field5_new, 0, sizeof(sz_buf_field5_new));
strcpy(sz_buf_field5_new, (0 == strcmp(sz_buf_field5, "m")) ? "BottomLayer" : "TopLayer");
// sz_buf_field6 is MICRO-USB-SMD_MICROXNJ
// sz_buf_field7_new is value (Designator + Comment)
memset(sz_buf_field7_new, 0, sizeof(sz_buf_field7_new));
// sprintf(sz_buf_field6_new, "%s_%s", sz_buf_field1, sz_buf_field5);
// 坐标文件本来就没有值, 留空吧, 看看能行不?
// 从bom文件中得到位号对应的私有料号
// sz_buf_field1 is J4
psz_tmp = sz_buf_field7_new;
get_bom_part_sn(g_list_bom_item, sz_buf_field1, &psz_tmp);
// 拼新行写入新文件
memset(sz_buf_write, 0, sizeof(sz_buf_write));
// BottomLayer
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
sprintf(sz_buf_write, "\"%s\", \"%.3fmm\", \"%.3fmm\", \"%.2f\", \"%s\", \"%s\", \"%s\"\n",
sz_buf_field1,
fX,
fY,
fAngle,
sz_buf_field5_new,
sz_buf_field6,
sz_buf_field7_new
);
fwrite(sz_buf_write, sizeof(char), strlen(sz_buf_write), fpFileNew);
}
double mil2mm(double fMil, bool bIsMil)
{
double fMm = fMil;
if (bIsMil)
{
// 100mil = 2.54mm
//
// 100mil / 2.54 = 2.54/2.54mm
// 1mm = 100mil / 2.54
//
// 1mil = 2.54mm / 100
//
fMm = fMil * 2.54 / 100;
}
return fMm;
}
void replace_char(char* pszSrc, char c_find, char c_new)
{
char* psz_find = NULL;
do {
if (NULL == pszSrc)
{
break;
}
psz_find = pszSrc;
do {
psz_find = strchr(psz_find, c_find);
if (NULL == psz_find)
{
break;
}
*psz_find = c_new;
psz_find++;
} while (true);
} while (false);
}
void replace_char_between_double_quotation_marks(char* pszSrc, char c_old, char c_new)
{
char* psz_Head = NULL;
char* psz_first_double_quotation = NULL;
char* psz_next_double_quotation = NULL;
char* psz_cur = NULL;
do {
if (NULL == pszSrc)
{
break;
}
psz_Head = pszSrc;
do {
psz_first_double_quotation = strchr(psz_Head, '"');
if (NULL == psz_first_double_quotation)
{
break;
}
psz_Head = psz_first_double_quotation + 1;
psz_next_double_quotation = strchr(psz_Head, '"');
if (NULL == psz_next_double_quotation)
{
break;
}
for (psz_cur = (psz_first_double_quotation + 1); psz_cur <= (psz_next_double_quotation - 1); psz_cur++)
{
if (*psz_cur == c_old)
{
*psz_cur = c_new;
}
}
psz_Head = psz_next_double_quotation + 1;
} while (true);
} while (false);
}
void update_part_items(std::list<std::string>& list, char* psz_item)
{
// psz_item like C1_C3_C7
char* psz_Head = NULL;
char* psz_find = NULL;
char sz_buf[4096];
list.clear();
memset(sz_buf, 0, sizeof(sz_buf));
strcpy(sz_buf, psz_item);
replace_char(sz_buf, '_', ' ');
psz_Head = sz_buf;
do {
psz_find = strchr(psz_Head, ' ');
if (NULL == psz_find)
{
list.push_back(psz_Head);
break;
}
else {
*psz_find = '\0';
list.push_back(psz_Head);
psz_find++;
psz_Head = psz_find;
}
} while (true);
}
bool process_orcad_bom_file(const char* pszOrcadBomFile)
{
/*
Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
1,1,BAT_CR1220-2,电池座_CR1220-2,电池底座 适用电池:CR1220,BAT1,BAT_0001
2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
*/
bool isProcessOk = false;
FILE* pfOrcadBomFile = NULL;
bool bFindHead = false;
char sz_buf_rd[1024 * 8];
char* pFind = NULL;
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
// 1,1,BAT_CR1220-2,电池座_CR1220-2,电池底座 适用电池:CR1220,BAT1,BAT_0001
char sz_buf_field1[1024];
char sz_buf_field2[1024];
char sz_buf_field3[1024];
char sz_buf_field4[1024];
char sz_buf_field5[1024];
char sz_buf_field6[1024];
char sz_buf_field7[1024];
int iCnt = 0;
bool isNeedBreak = false;
bool b_head_item_ok_field1 = false;
bool b_head_item_ok_field2 = false;
bool b_head_item_ok_field3 = false;
bool b_head_item_ok_field4 = false;
bool b_head_item_ok_field5 = false;
bool b_head_item_ok_field6 = false;
bool b_head_item_ok_field7 = false;
TAG_BOM_ITEM* pBomItem = NULL;
int i_debug_cnt = 0;
do {
if (NULL == pszOrcadBomFile)
{
printf("err : orcad BOM文件名为空\r\n");
break;
}
pfOrcadBomFile = fopen(pszOrcadBomFile, "r");
if (NULL == pfOrcadBomFile)
{
printf("err : 打开文件(%s)失败\r\n", pszOrcadBomFile);
break;
}
do {
i_debug_cnt++;
if (21 == i_debug_cnt)
{
i_debug_cnt--;
i_debug_cnt++;
}
memset(sz_buf_rd, 0, sizeof(sz_buf_rd));
pFind = fgets(sz_buf_rd, sizeof(sz_buf_rd), pfOrcadBomFile);
if (NULL == pFind)
{
isNeedBreak = true;
printf("orcad bom 文件 : 读取完毕\r\n");
break;
}
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
memset(sz_buf_field2, 0, sizeof(sz_buf_field2));
memset(sz_buf_field3, 0, sizeof(sz_buf_field3));
memset(sz_buf_field4, 0, sizeof(sz_buf_field4));
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
memset(sz_buf_field6, 0, sizeof(sz_buf_field6));
memset(sz_buf_field7, 0, sizeof(sz_buf_field7));
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number\n
// 替换双引号之间的字符(e.g. ','), 因为分隔符号是',', 所以内容中不能有','
replace_char_between_double_quotation_marks(sz_buf_rd, ',', '_');
// replace_char_between_double_quotation_marks(sz_buf_rd, ' ', '_');
replace_char(sz_buf_rd, ' ', '_'); // sscanf 不支持内容中间为空格, 换成'_'
replace_char(sz_buf_rd, ',', ' '); // sscanf 不支持','作为分隔符号, 换成' '
iCnt = sscanf(sz_buf_rd, "%s %s %s %s %s %s %s",
sz_buf_field1,
sz_buf_field2,
sz_buf_field3,
sz_buf_field4,
sz_buf_field5,
sz_buf_field6,
sz_buf_field7);
if (iCnt <= 0)
{
continue;
}
if (7 != iCnt)
{
isNeedBreak = true;
isProcessOk = false;
printf("err : 文件格式不符合预期\r\n");
break;
}
if (!bFindHead)
{
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
b_head_item_ok_field1 = (0 == strcmp(sz_buf_field1, "Item_Number"));
b_head_item_ok_field2 = (0 == strcmp(sz_buf_field2, "Quantity"));
b_head_item_ok_field3 = (0 == strcmp(sz_buf_field3, "Value"));
b_head_item_ok_field4 = (0 == strcmp(sz_buf_field4, "Value_BOM"));
b_head_item_ok_field5 = (0 == strcmp(sz_buf_field5, "Description"));
b_head_item_ok_field6 = (0 == strcmp(sz_buf_field6, "Part_Reference"));
b_head_item_ok_field7 = (0 == strcmp(sz_buf_field7, "Part_Number"));
if (!b_head_item_ok_field1 ||
!b_head_item_ok_field2 ||
!b_head_item_ok_field3 ||
!b_head_item_ok_field4 ||
!b_head_item_ok_field5 ||
!b_head_item_ok_field6 ||
!b_head_item_ok_field7)
{
isNeedBreak = true;
isProcessOk = false;
printf("err : 文件格式(题头)不符合预期\r\n");
break;
}
bFindHead = true;
}
else {
pBomItem = new TAG_BOM_ITEM;
if (NULL != pBomItem)
{
strcpy(pBomItem->sz_ItemNumber, sz_buf_field1); // "Item Number"
pBomItem->i_Quantity = atoi(sz_buf_field2); // "Quantity"
strcpy(pBomItem->sz_Value, sz_buf_field3); // "Value"
strcpy(pBomItem->sz_Value_BOM, sz_buf_field4); // "Value_BOM"
strcpy(pBomItem->sz_Description, sz_buf_field5); // "Description"
strcpy(pBomItem->sz_Part_Number, sz_buf_field7); // "Part_Number"
// C1_C3_C7
update_part_items(pBomItem->list_Part, sz_buf_field6); // 将元件位号取出来, 存入list
g_list_bom_item.push_back(pBomItem);
}
isProcessOk = true;
}
} while (!isNeedBreak);
} while (0);
if (NULL != pfOrcadBomFile)
{
fclose(pfOrcadBomFile);
pfOrcadBomFile = NULL;
}
return isProcessOk;
}
void clear_bom_list(void)
{
// std::list<TAG_BOM_ITEM*> g_list_bom_item; // 将BOM文件载入到这个list供查询
std::list<TAG_BOM_ITEM*>::iterator it;
TAG_BOM_ITEM* pItem = NULL;
while (g_list_bom_item.size() > 0)
{
it = g_list_bom_item.begin();
pItem = *it;
g_list_bom_item.pop_front();
if (NULL != pItem)
{
delete pItem;
pItem = NULL;
}
}
}
void get_bom_part_sn(std::list<TAG_BOM_ITEM*>& list, char* psz_part_id, char** ppsz_field)
{
std::list<TAG_BOM_ITEM*>::iterator it;
std::list<std::string>::iterator it_part_sn;
bool b_find = false;
// 2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
for (it = list.begin(); it != list.end(); it++)
{
for (it_part_sn = (*it)->list_Part.begin(); it_part_sn != (*it)->list_Part.end(); it_part_sn++)
{
if (0 == (*it_part_sn).compare(psz_part_id))
{
strcpy(*ppsz_field, (*it)->sz_Part_Number); // 将私有料号放前面, 在openpnp料站中看的更清楚
strcat(*ppsz_field, "_");
strcat(*ppsz_field, (*it)->sz_Value);
// ppsz_field like "CON_0006_usb_skt_smd_MICROXNJ1"
b_find = true;
break;
}
}
if (b_find)
{
break;
}
}
}
产生的适合openpnp的坐标文件openpnp_named_csv.csv, 样例如下
openpnp Named csv Pick and Place Locations
org file = allegro_org.txt
convert file = openpnp_named_csv.csv
========================================================================================================================
File Design Information:
Units used: mm
"Designator","Ref-X(mm)","Ref-Y(mm)","Rotation","Layer", "Footprint","Comment"
"J4", "3.988mm", "22.835mm", "270.00", "TopLayer", "MICRO-USB-SMD_MICROXNJ", "CON_0006_usb_skt_smd_MICROXNJ1"
"TP1", "147.549mm", "82.147mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP10", "102.895mm", "59.893mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP11", "67.818mm", "90.678mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP12", "71.628mm", "90.653mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP2", "147.574mm", "78.232mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP3", "51.308mm", "72.568mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP4", "47.498mm", "72.644mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP5", "47.498mm", "36.712mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP6", "51.181mm", "36.779mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP7", "83.479mm", "5.080mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP8", "87.122mm", "5.080mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"TP9", "99.162mm", "59.868mm", "0.00", "TopLayer", "TEST_TH_PIN_1D0MM", "M_0010_test_th_pin_1d0mm"
"M1", "96.698mm", "85.293mm", "180.00", "TopLayer", "STC15_EXP_BOX4_MCU_BOARD_FP_V1", "MODULE_0001_STC15_EXP_BOX4_IF_2.54mmx11x2socket_C132126_2cnt"
"LOGO2", "96.012mm", "30.988mm", "0.00", "BottomLayer", "LOGO_KRGY", "M_0009_logo_krgy"
"LOGO1", "37.272mm", "13.077mm", "0.00", "TopLayer", "LOGO_STC15_BOX4", "M_0008_logo_stc15_box4"
"DW2", "3.531mm", "15.519mm", "0.00", "TopLayer", "DW_HOLE_D1R0MM_D2R0MM", "M_0003_dw_hole_1d0mm"
备注
下一步, 就可以向openpnp设备料站平台上放置物料(自动飞达编带料,固定短编带散料,托盘散料), 通过openpnp上位机软件, 对元件指定料站.
补充 - 2023_0214_1733
今天同学让我将allegro格式的坐标文件转成openpnp格式的坐标文件, 突然发现转的不对, 单步调试后, 修正后, 可以了.
发现有3个问题:
- 坐标文件要SPB17.4的新格式, 不能是16.3的旧格式.
- BOM只能出6列指定的题头(还没整成一个正式工具, 凑合着能用就行)
- 程序判断坐标文件列数有点问题, 改一下.
有关于这3点的修正记录
坐标文件格式
必须采用SPB17.4新格式, 使用空格分隔每列的.
旧格式用 ! 来分隔每列, 俺的工具程序没有搞那么细致, 不支持.
BOM列头必须是指定的列名, 而且列名的前后顺序必须一致
为了知道在哪里买料, 还需要将供应商料号加在尾部, 和出坐标文件无关.
程序修正一下
程序只是作为临时工具, 很多地方判断不细致, 达不到给别人用的地步, 自己用用还凑合.
// @file openpnp_named_cvs_convert.cpp
// @brief 转换SPB17.4 allegro导出的坐标文件(新格式) + orcad BOM单 转换为openpnp可导入的坐标文件
// @note 编译环境 vs2022 vc++ console
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <list>
#include <string>
#define OPENPNP_NAMED_CSV_FILE "openpnp_named_csv.csv"
// 2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
typedef struct _tag_bom_item {
char sz_ItemNumber[0x100];
int i_Quantity;
char sz_Value[0x100];
char sz_Value_BOM[0x100];
char sz_Description[0x100];
char sz_Part_Number[0x100];
// Part Reference
std::list<std::string> list_Part;
}TAG_BOM_ITEM;
std::list<TAG_BOM_ITEM*> g_list_bom_item; // 将BOM文件载入到这个list供查询
void clear_bom_list(void);
void replace_char(char* pszSrc, char c_find, char c_new);
void replace_char_between_double_quotation_marks(char* pszSrc, char c_old, char c_new);
void get_bom_part_sn(std::list<TAG_BOM_ITEM*>& list, char* psz_part_id, char** ppsz_field);
void process_allegro_csv(const char* pszAllegroFile, const char* pszOrcadBomFile, const char* pszOpenpnpCsvFile);
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew);
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil);
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6);
double mil2mm(double fMil, bool bIsMil);
bool process_orcad_bom_file(const char* pszOrcadBomFile);
int main(int argc, char** argv)
{
char* pszAllegroFile = NULL; // allegro 坐标文件
char* pszOrcadBomFile = NULL; // orcad 料单
char sz_openpnp_file[256]; // 转换完成后的openpnp坐标文件
do {
if (3 != argc)
{
// 测试用的 allegro导出的原始坐标文件为 allegro_org.txt
printf("请给出要转换的csv文件名称\r\n");
printf("usage : openpnp_named_cvs_convert allegro_org.txt BOM_orcad_org.csv\r\n");
printf("allegro_org.txt 是allegro导出的坐标文件\r\n");
printf("BOM_orcad_org.csv 是orcad导出的料单\r\n");
printf("%s 是转换后的openpnp可导入的最终坐标文件\r\n", OPENPNP_NAMED_CSV_FILE);
break;
}
if (NULL == argv[1])
{
printf("err : 给定的Allegro坐标文件名称为空\r\n");
break;
}
pszAllegroFile = argv[1];
printf("要处理的Allegro坐标文件为 : %s\r\n", pszAllegroFile);
if (NULL == argv[2])
{
printf("err : 给定的Orcad料单文件名称为空\r\n");
break;
}
pszOrcadBomFile = argv[2];
printf("要处理的Orcad料单文件为 : %s\r\n", pszOrcadBomFile);
memset(sz_openpnp_file, 0, sizeof(sz_openpnp_file));
sprintf(sz_openpnp_file, "%s", OPENPNP_NAMED_CSV_FILE);
printf("预期转换完的openpnp坐标文件为 : %s\r\n", sz_openpnp_file);
process_allegro_csv(pszAllegroFile, pszOrcadBomFile, sz_openpnp_file);
printf("处理结束 : 请查看转换完的openpnp坐标文件 %s 是否处理正确\r\n", sz_openpnp_file);
} while (0);
clear_bom_list();
return EXIT_SUCCESS;
}
void process_allegro_csv(const char* pszAllegroFile, const char* pszOrcadBomFile, const char* pszOpenpnpCsvFile)
{
FILE* fpAllegroFile = NULL;
do {
// 在编译选项(预处理器)中加入 _CRT_SECURE_NO_WARNINGS 宏, 防止编译警告
fpAllegroFile = fopen(pszAllegroFile, "r");
if (NULL == fpAllegroFile)
{
printf("err : Allegro坐标文件(%s)打开失败\r\n", pszAllegroFile);
break;
}
if (!process_orcad_bom_file(pszOrcadBomFile))
{
printf("err : 处理orcad BOM文件(%s)失败\r\n", pszOrcadBomFile);
break;
}
process_allegro_csv(pszAllegroFile, fpAllegroFile, pszOpenpnpCsvFile);
} while (0);
if (NULL != fpAllegroFile)
{
fclose(fpAllegroFile);
fpAllegroFile = NULL;
}
}
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew)
{
char sz_buf_rd[1024];
size_t nRdCnt = 0;
char* pFind = NULL;
char sz_buf_field1[0x100];
char sz_buf_field2[0x100];
char sz_buf_field3[0x100];
char sz_buf_field4[0x100];
char sz_buf_field5[0x100];
char sz_buf_field6[0x100];
int iCnt = 0;
bool bUnitFind = false;
bool bUnitIsMm = false;
bool bUnitIsMil = false;
FILE* fpFileNew = NULL;
do {
if (NULL == pFileNew)
{
break;
}
if (NULL == fpFileNew)
{
fpFileNew = fopen(pFileNew, "w");
if (NULL == fpFileNew)
{
printf("建立新文件(%s)失败\r\n", pFileNew);
break;
}
}
memset(sz_buf_rd, 0, sizeof(sz_buf_rd));
pFind = fgets(sz_buf_rd, sizeof(sz_buf_rd), fpFile);
if (NULL == pFind)
{
printf("alegro坐标文件 : 读取完毕\r\n");
break;
}
if (!bUnitFind)
{
// 查找 UUNITS = MILS
pFind = strstr(sz_buf_rd, "UUNITS = ");
if (NULL == pFind)
{
continue;
}
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
iCnt = sscanf(sz_buf_rd, "UUNITS = %255s", sz_buf_field1); // MILS
if (iCnt <= 0)
{
continue;
}
if (0 == strcmp(sz_buf_field1, "MILS"))
{
// 单位是mil
bUnitFind = true;
bUnitIsMm = false;
bUnitIsMil = true;
}
else if (0 == strcmp(sz_buf_field1, "MM"))
{
// 单位是mm
bUnitFind = true;
bUnitIsMm = true;
bUnitIsMil = false;
}
else {
printf("err : 文件格式不对, 单位应该是mil和mm其中一个\r\n");
break;
}
// 写处理完的csv的注释部分
process_convert_head(pFile, fpFile, pFileNew, fpFileNew, bUnitFind, bUnitIsMm, bUnitIsMil);
}
else {
// 查找 J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
//
// 如果有背面元件时, 就有6列, 会在第5列多出一个镜像的m字符, 第6列是封装
// 如果是正面的元件, 就只有5列
// LOGO2 3780.00 1220.00 0 m LOGO_KRGY
// LOGO1 1467.42 514.83 0 LOGO_STC15_BOX4
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
memset(sz_buf_field2, 0, sizeof(sz_buf_field2));
memset(sz_buf_field3, 0, sizeof(sz_buf_field3));
memset(sz_buf_field4, 0, sizeof(sz_buf_field4));
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
memset(sz_buf_field6, 0, sizeof(sz_buf_field6));
iCnt = sscanf(sz_buf_rd, "%s %s %s %s %s %s",
sz_buf_field1,
sz_buf_field2,
sz_buf_field3,
sz_buf_field4,
sz_buf_field5,
sz_buf_field6); // MILS
if ((iCnt != 5) && (iCnt != 6))
{
continue;
}
if (5 == iCnt)
{
// 是正面元件, 第5列为空, 只有第6列为封装
strcpy(sz_buf_field6, sz_buf_field5);
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
}
else if (6 == iCnt) {
// 是反面面元件, 第5列为m, 第6列为封装
}
// refdes sz_buf_field2
// symbol_x sz_buf_field4
// symbol_y sz_buf_field6
if ((0 == strcmp("refdes", sz_buf_field2))
&& (0 == strcmp("symbol_x", sz_buf_field4))
&& (0 == strcmp("symbol_y", sz_buf_field6)))
{
continue;
}
process_convert_body(fpFileNew, bUnitIsMm, sz_buf_field1, sz_buf_field2, sz_buf_field3, sz_buf_field4, sz_buf_field5, sz_buf_field6);
}
} while (1);
if (NULL != fpFileNew)
{
fclose(fpFileNew);
fpFileNew = NULL;
}
}
void process_convert_head(const char* pFile, FILE* fpFile, const char* pFileNew, FILE* fpFileNew, bool bUnitFind, bool bUnitIsMm, bool bUnitIsMil)
{
// 在文件的前10行(只能是前10行, openpnp有规定)写入一些注释(e.g. 我是谁? 我在哪?)
/*
Altium Designer Pick and Place Locations
Z:\test\Pick Place for NEW_PCB_2022-11-12.csv
========================================================================================================================
File Design Information:
Date: 12/11/22
Time: 13:42
Revision: Not in VersionControl
Variant: No variations
Units used: mm
*/
char sz_buf[1024];
do {
if ((NULL == fpFile) || (NULL == fpFile) || (!bUnitFind))
{
break;
}
// bool bUnitIsMm, bool bUnitIsMil
if (!bUnitIsMm && !bUnitIsMil)
{
// mm 和 mil 单位必须有一个为true
break;
}
// line 1
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "openpnp Named csv Pick and Place Locations\n"); // 以w方式打开的文件, 换行的话,\n就行了
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 2
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "org file = %s\n", pFile);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 3
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "convert file = %s\n", pFileNew);
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 4
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "========================================================================================================================\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 5
memset(sz_buf, 0, sizeof(sz_buf));
sprintf(sz_buf, "File Design Information:\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 6
memset(sz_buf, 0, sizeof(sz_buf));
// Units used: mm
sprintf(sz_buf, "Units used: mm\n"); // openpnp用的坐标文件, 必须是mm
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
// line 7
// J4 157.00 899.00 270 MICRO-USB-SMD_MICROXNJ
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Comment"
// "Designator","Comment","Footprint","Rotation","Ref-X(mm)","Ref-Y(mm)" // 这个是用AD出的坐标文件整理后的可用题头
// "Designator", "Comment", "Layer", "Footprint", "Center-X(mm)", "Center-Y(mm)", "Rotation", "Description", "Ref-X(mm)", "Ref-Y(mm)", "Pad-X(mm)", "Pad-Y(mm)"
// "U1", "BAT-SMD_CR1220-2", "TopLayer", "BAT-SMD_CR1220-2", "58.2270mm", "68.2269mm", "0", "", "58.2270mm", "68.2269mm", "50.2270mm", "68.2269mm"
// 发现allegro导出的坐标文件, 是分正反面的, 所以要加上Layer字段
memset(sz_buf, 0, sizeof(sz_buf));
// 前6列是allegro导出的坐标文件就有的, 第7列是拼装的(位号 + 封装), 或留空(因为allegro导出的坐标文件,本来就没有元件值)
// 列1 列2 列3 列4 列5 列6 列7
sprintf(sz_buf, "\"Designator\",\"Ref-X(mm)\",\"Ref-Y(mm)\",\"Rotation\",\"Layer\", \"Footprint\",\"Comment\"\n");
fwrite(sz_buf, sizeof(char), strlen(sz_buf), fpFileNew);
} while (0);
}
void process_convert_body(FILE* fpFileNew, bool bUnitIsMm,
char* sz_buf_field1, char* sz_buf_field2, char* sz_buf_field3, char* sz_buf_field4, char* sz_buf_field5, char* sz_buf_field6)
{
// 原始
// "Designator","X (mm)","Y (mm)","Rotation","Footprint","Layer","Comment"
// J4 157.00 899.00 270 m MICRO-USB-SMD_MICROXNJ
// 转换之后
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "BottomLayer", "10uF"
// "C2", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
char sz_buf_field5_new[0x100];
char sz_buf_field7_new[0x100];
char sz_buf_write[1024];
double fTmp = 0;
double fX = 0;
double fY = 0;
double fAngle = 0;
char* psz_tmp = NULL;
// sz_buf_field1 is J4
// sz_buf_field2 is 157.00
fTmp = atof(sz_buf_field2);
fX = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field3 is 899.00
fTmp = atof(sz_buf_field3);
fY = mil2mm(fTmp, !bUnitIsMm);
// sz_buf_field4 is 270
fAngle = atof(sz_buf_field4);
// sz_buf_field5 is m // 正反面, 反面为m 正面为空
memset(sz_buf_field5_new, 0, sizeof(sz_buf_field5_new));
strcpy(sz_buf_field5_new, (0 == strcmp(sz_buf_field5, "m")) ? "BottomLayer" : "TopLayer");
// sz_buf_field6 is MICRO-USB-SMD_MICROXNJ
// sz_buf_field7_new is value (Designator + Comment)
memset(sz_buf_field7_new, 0, sizeof(sz_buf_field7_new));
// sprintf(sz_buf_field6_new, "%s_%s", sz_buf_field1, sz_buf_field5);
// 坐标文件本来就没有值, 留空吧, 看看能行不?
// 从bom文件中得到位号对应的私有料号
// sz_buf_field1 is J4
psz_tmp = sz_buf_field7_new;
get_bom_part_sn(g_list_bom_item, sz_buf_field1, &psz_tmp);
// 拼新行写入新文件
memset(sz_buf_write, 0, sizeof(sz_buf_write));
// BottomLayer
// "C1", "58.674mm", "7.239mm", "90.00", "NICHICON_A", "TopLayer", "10uF"
sprintf(sz_buf_write, "\"%s\", \"%.3fmm\", \"%.3fmm\", \"%.2f\", \"%s\", \"%s\", \"%s\"\n",
sz_buf_field1,
fX,
fY,
fAngle,
sz_buf_field5_new,
sz_buf_field6,
sz_buf_field7_new
);
fwrite(sz_buf_write, sizeof(char), strlen(sz_buf_write), fpFileNew);
}
double mil2mm(double fMil, bool bIsMil)
{
double fMm = fMil;
if (bIsMil)
{
// 100mil = 2.54mm
//
// 100mil / 2.54 = 2.54/2.54mm
// 1mm = 100mil / 2.54
//
// 1mil = 2.54mm / 100
//
fMm = fMil * 2.54 / 100;
}
return fMm;
}
void replace_char(char* pszSrc, char c_find, char c_new)
{
char* psz_find = NULL;
do {
if (NULL == pszSrc)
{
break;
}
psz_find = pszSrc;
do {
psz_find = strchr(psz_find, c_find);
if (NULL == psz_find)
{
break;
}
*psz_find = c_new;
psz_find++;
} while (true);
} while (false);
}
void replace_char_between_double_quotation_marks(char* pszSrc, char c_old, char c_new)
{
char* psz_Head = NULL;
char* psz_first_double_quotation = NULL;
char* psz_next_double_quotation = NULL;
char* psz_cur = NULL;
do {
if (NULL == pszSrc)
{
break;
}
psz_Head = pszSrc;
do {
psz_first_double_quotation = strchr(psz_Head, '"');
if (NULL == psz_first_double_quotation)
{
break;
}
psz_Head = psz_first_double_quotation + 1;
psz_next_double_quotation = strchr(psz_Head, '"');
if (NULL == psz_next_double_quotation)
{
break;
}
for (psz_cur = (psz_first_double_quotation + 1); psz_cur <= (psz_next_double_quotation - 1); psz_cur++)
{
if (*psz_cur == c_old)
{
*psz_cur = c_new;
}
}
psz_Head = psz_next_double_quotation + 1;
} while (true);
} while (false);
}
void update_part_items(std::list<std::string>& list, char* psz_item)
{
// psz_item like C1_C3_C7
char* psz_Head = NULL;
char* psz_find = NULL;
char sz_buf[4096];
list.clear();
memset(sz_buf, 0, sizeof(sz_buf));
strcpy(sz_buf, psz_item);
replace_char(sz_buf, '_', ' ');
psz_Head = sz_buf;
do {
psz_find = strchr(psz_Head, ' ');
if (NULL == psz_find)
{
list.push_back(psz_Head);
break;
}
else {
*psz_find = '\0';
list.push_back(psz_Head);
psz_find++;
psz_Head = psz_find;
}
} while (true);
}
bool process_orcad_bom_file(const char* pszOrcadBomFile)
{
/*
Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
1,1,BAT_CR1220-2,电池座_CR1220-2,电池底座 适用电池:CR1220,BAT1,BAT_0001
2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
*/
bool isProcessOk = false;
FILE* pfOrcadBomFile = NULL;
bool bFindHead = false;
char sz_buf_rd[1024 * 8];
char* pFind = NULL;
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
// 1,1,BAT_CR1220-2,电池座_CR1220-2,电池底座 适用电池:CR1220,BAT1,BAT_0001
char sz_buf_field1[1024];
char sz_buf_field2[1024];
char sz_buf_field3[1024];
char sz_buf_field4[1024];
char sz_buf_field5[1024];
char sz_buf_field6[1024];
char sz_buf_field7[1024];
int iCnt = 0;
bool isNeedBreak = false;
bool b_head_item_ok_field1 = false;
bool b_head_item_ok_field2 = false;
bool b_head_item_ok_field3 = false;
bool b_head_item_ok_field4 = false;
bool b_head_item_ok_field5 = false;
bool b_head_item_ok_field6 = false;
bool b_head_item_ok_field7 = false;
TAG_BOM_ITEM* pBomItem = NULL;
int i_debug_cnt = 0;
do {
if (NULL == pszOrcadBomFile)
{
printf("err : orcad BOM文件名为空\r\n");
break;
}
pfOrcadBomFile = fopen(pszOrcadBomFile, "r");
if (NULL == pfOrcadBomFile)
{
printf("err : 打开文件(%s)失败\r\n", pszOrcadBomFile);
break;
}
do {
i_debug_cnt++;
if (21 == i_debug_cnt)
{
i_debug_cnt--;
i_debug_cnt++;
}
memset(sz_buf_rd, 0, sizeof(sz_buf_rd));
pFind = fgets(sz_buf_rd, sizeof(sz_buf_rd), pfOrcadBomFile);
if (NULL == pFind)
{
isNeedBreak = true;
printf("orcad bom 文件 : 读取完毕\r\n");
break;
}
memset(sz_buf_field1, 0, sizeof(sz_buf_field1));
memset(sz_buf_field2, 0, sizeof(sz_buf_field2));
memset(sz_buf_field3, 0, sizeof(sz_buf_field3));
memset(sz_buf_field4, 0, sizeof(sz_buf_field4));
memset(sz_buf_field5, 0, sizeof(sz_buf_field5));
memset(sz_buf_field6, 0, sizeof(sz_buf_field6));
memset(sz_buf_field7, 0, sizeof(sz_buf_field7));
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number\n
// 替换双引号之间的字符(e.g. ','), 因为分隔符号是',', 所以内容中不能有','
replace_char_between_double_quotation_marks(sz_buf_rd, ',', '_');
// replace_char_between_double_quotation_marks(sz_buf_rd, ' ', '_');
replace_char(sz_buf_rd, ' ', '_'); // sscanf 不支持内容中间为空格, 换成'_'
replace_char(sz_buf_rd, ',', ' '); // sscanf 不支持','作为分隔符号, 换成' '
iCnt = sscanf(sz_buf_rd, "%s %s %s %s %s %s %s",
sz_buf_field1,
sz_buf_field2,
sz_buf_field3,
sz_buf_field4,
sz_buf_field5,
sz_buf_field6,
sz_buf_field7);
if (iCnt <= 0)
{
continue;
}
if (7 != iCnt)
{
isNeedBreak = true;
isProcessOk = false;
printf("err : 文件格式不符合预期\r\n");
break;
}
if (!bFindHead)
{
// Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
b_head_item_ok_field1 = (0 == strcmp(sz_buf_field1, "Item_Number"));
b_head_item_ok_field2 = (0 == strcmp(sz_buf_field2, "Quantity"));
b_head_item_ok_field3 = (0 == strcmp(sz_buf_field3, "Value"));
b_head_item_ok_field4 = (0 == strcmp(sz_buf_field4, "Value_BOM"));
b_head_item_ok_field5 = (0 == strcmp(sz_buf_field5, "Description"));
b_head_item_ok_field6 = (0 == strcmp(sz_buf_field6, "Part_Reference"));
b_head_item_ok_field7 = (0 == strcmp(sz_buf_field7, "Part_Number"));
if (!b_head_item_ok_field1 ||
!b_head_item_ok_field2 ||
!b_head_item_ok_field3 ||
!b_head_item_ok_field4 ||
!b_head_item_ok_field5 ||
!b_head_item_ok_field6 ||
!b_head_item_ok_field7)
{
isNeedBreak = true;
isProcessOk = false;
printf("err : 文件格式(题头)不符合预期\r\n");
break;
}
bFindHead = true;
}
else {
pBomItem = new TAG_BOM_ITEM;
if (NULL != pBomItem)
{
strcpy(pBomItem->sz_ItemNumber, sz_buf_field1); // "Item Number"
pBomItem->i_Quantity = atoi(sz_buf_field2); // "Quantity"
strcpy(pBomItem->sz_Value, sz_buf_field3); // "Value"
strcpy(pBomItem->sz_Value_BOM, sz_buf_field4); // "Value_BOM"
strcpy(pBomItem->sz_Description, sz_buf_field5); // "Description"
strcpy(pBomItem->sz_Part_Number, sz_buf_field7); // "Part_Number"
// C1_C3_C7
update_part_items(pBomItem->list_Part, sz_buf_field6); // 将元件位号取出来, 存入list
g_list_bom_item.push_back(pBomItem);
}
isProcessOk = true;
}
} while (!isNeedBreak);
} while (0);
if (NULL != pfOrcadBomFile)
{
fclose(pfOrcadBomFile);
pfOrcadBomFile = NULL;
}
return isProcessOk;
}
void clear_bom_list(void)
{
// std::list<TAG_BOM_ITEM*> g_list_bom_item; // 将BOM文件载入到这个list供查询
std::list<TAG_BOM_ITEM*>::iterator it;
TAG_BOM_ITEM* pItem = NULL;
while (g_list_bom_item.size() > 0)
{
it = g_list_bom_item.begin();
pItem = *it;
g_list_bom_item.pop_front();
if (NULL != pItem)
{
delete pItem;
pItem = NULL;
}
}
}
void get_bom_part_sn(std::list<TAG_BOM_ITEM*>& list, char* psz_part_id, char** ppsz_field)
{
std::list<TAG_BOM_ITEM*>::iterator it;
std::list<std::string>::iterator it_part_sn;
bool b_find = false;
// 2,3,47uF/20%/50V/SMD/6.3x7.7mm,47uF/±20%/50V/SMD/6.3x7.7mm,贴片型铝电解电容 47uF ±20% 50V SMD 6.3x7.7mm,C1 C3 C7,CAP_0001
for (it = list.begin(); it != list.end(); it++)
{
for (it_part_sn = (*it)->list_Part.begin(); it_part_sn != (*it)->list_Part.end(); it_part_sn++)
{
if (0 == (*it_part_sn).compare(psz_part_id))
{
strcpy(*ppsz_field, (*it)->sz_Part_Number); // 将私有料号放前面, 在openpnp料站中看的更清楚
strcat(*ppsz_field, "_");
strcat(*ppsz_field, (*it)->sz_Value);
// ppsz_field like "CON_0006_usb_skt_smd_MICROXNJ1"
b_find = true;
break;
}
}
if (b_find)
{
break;
}
}
}
补充 - 生成坐标前, 一定要将allegro原点设置到板子左下角
做了一块pnp_test板子, 做板子时, 就顺手生成了坐标文件.
设备调试好了, 想试贴一下. 发现板子的Mark点都不对, 但是方向是对的.
查了一下, 原来是画板子时, 坐标在板子中心.
将板子坐标设置到板子左下角, 然后再出坐标文件才能符合openpnp的要求.