cadence SPB17.4 - export placement file to openpnp

2023/11/30 10:12:59

文章目录

    • cadence SPB17.4 - export placement file to openpnp
    • 概述
    • 笔记
    • openpnp支持的坐标文件格式
    • openpnp导入坐标文件的内容规则
    • 制作openpnp可用的坐标文件的思路列表
    • 先自己写个程序试试, 这个不难, 也省心
    • 程序写完了,好使
    • 转换前的allego原版坐标文件
    • 转换后的openpnp格式的坐标文件
    • 被openpnp载入后的效果
    • 发现的问题
    • orcad导出料单
    • 程序维护完成,好使
    • 备注
    • 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上位机软件, 对元件指定料站.

END


http://www.jnnr.cn/a/126822.html

相关文章

java项目-第127期SpringBoot+vue的智慧养老手表管理系统-java毕业设计_计算机毕业设计

java项目-第127期SpringBootvue的智慧养老手表管理系统-java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 本系统主要是通过智能手表监控老人的日常生活&#xff0c;比如血压、心率、睡眠步数、以及摔倒情况。 共分为两个角色&#xff1a;家长&#xff0c;养老院管理员&a…

Linux基础IO(二)

文章目录再谈文件描述符fd文件是怎么被访问的&#xff1f;文件描述符的分配规则输出重定向输出重定向证明输出重定向原理输入重定向&#xff1b;输入重定向证明输入重定向模拟实现什么叫做文件呢&#xff1f;如何用C语言实现面向对象总述打开文件流程初谈缓冲区为什么要有缓冲区…

Shell逻辑判断、分支语句(带案例,Shell脚本学习笔记)

逻辑语法&#xff1a; if [ 判断条件 ] ; then .... elif [ 判断条件 ]; then .... else .... fi (其中fi是结束标志) 踩坑提醒&#xff1a; 大括号里面写条件判断前&#xff0c;前后都要空一格&#xff0c;比如 &#xff0c;错误写法&#xff1a;[$a $b] &#xff0c;…

【linux命令】链接/用户组/find/xargs/grep

1、链接 添加硬链接,硬链接的大小和原文件大小一样&#xff0c;内容是同步的 ln file1 file1.hard 添加软链接&#xff0c;软链接的大小是指向文件的名字的长度&#xff08;file1就是5个字节&#xff0c;/root/file1就是11个字节&#xff09; ln -s /root/file1 file1.soft_2…

Vue3 - style v-bind 动态样式(详细使用教程)

前言 对比 Vue2 &#xff0c;引出并展开 Vue3 。 本文讲述了 style v-bind 动态样式是什么&#xff0c;以及使用方法和代码示例。 简介 相信大家对于这块的理解肯定没问题&#xff0c;所以不过多解释了。 简单来说就是把我们 script 中的数据可以在 style 标签中使用&#xff…

leetcode:1533. 找到最大整数的索引【奇特的二分】

目录题目截图题目分析ac code总结题目截图 题目分析 l, r, x, y从最大开始二分i和j分奇数or偶数 关注mid 还是 mid 1考虑l, r, x, y i, mid, mid(mid 1), j考虑compareSub(l, r, x, y)若为1&#xff0c;大的在l, r 更新i j若为-1&#xff0c;大的x, y 更新i j若为0&#xf…

[m0leCon beginner 2022] 部分

目录 Crypto unrecognizeable Determination is key Magic,not crypto 未完成 SSA Transfers notes 没看懂,给了个网站太长了 Rev m0leadventures 未完成, lineary ptmsafe 差点 scramble MISC fileRecovery 睡一觉醒来&#xff0c;比赛已经结束了&#xff0c;还以…

Python循环部分学习总结

一、循环流程展示图&#xff1a; 以上是循环的一个简略图。 二、循环的类型 Python 提供了 for 循环和 while 循环&#xff08;在 Python 中没有 do…while 循环&#xff09; 1.while循环&#xff1a; 在给定的判断条件为 true 时执行循环体&#xff0c;否则退出循环体。 2.f…

SpringBoot业务开发 05、SpringBoot集成JSR303实现参数校验+全局异常捕捉

文章目录前言一、认识JSR303注解二、优雅入参校验引入校验器依赖2.1、实现基本的入参校验异常处理&#xff08;思路代码&#xff09;2.2、实现自定义参数校验注解2.3、实现分组校验&#xff08;多场景的复杂校验&#xff0c;分析代码&#xff09;三、全局异常捕捉&#xff08;完…

一文带你快速掌握面向对象三大特性之【多态】

1. 多态的概念 通俗来讲&#xff0c;就是多种形态。就是完成某一个行为时&#xff0c;如果不同对象去完成就会产生不同的状态。 总的来讲&#xff0c;同一件事情&#xff0c;发生在不同对象的身上&#xff0c;就会产生不同的结果。 2. 多态的实现条件 1、必须在继承类下 &am…

微机原理题笔记(真值,8253,中断,DMA控制器)

1:间接寻址方式中&#xff0c;操作数在&#xff08;B&#xff09;中 A通用寄存器 B内存单元 C程序计数器 D堆栈 补充&#xff1a;如果问你存储器数[带中括号&#xff0c;或者变量名例如 MOV AL,BUF]&#xff0c;不是在[BP]那么一定在数据段 有超越前缀看超越前缀&#xff…

jQuery源码

jQuery源码 jQuery的优势&#xff1a; 不用自己写循环 一路链式写法 大大提高了效率 转义及编码&#xff08;\u, \x&#xff09; - 邹天得 - 博客园 基本概括 整体骨架 支持commonjs规范 jQuery.fn&#xff1a;是给jQuery的prototype取了别名 jQuery是一个类&#xff0c;重构原…

SpringMVC—笔记总结

关于俺自己学习网课的笔记记录~ 文章目录1.SpringMVC1.1Spring第一个程序1.2Spring执行原理&#xff08;配置版&#xff09;1.3注解版2.Controller及RestFul风格2.1Controller控制器2.2实现Controller接口2.3使用注解Controller2.4RestFul风格3.SpringMVC:结果跳转3.1ModelAndV…

【数字电路基础】CMOS晶体管的延时

目录前言一、延时原因二、影响电路延时的因素(PVT)前言 本篇文章重点讲解数字电路中的有关时间的基本概念 一、延时原因 以反相器为例&#xff0c;A输入端口的电压变化时&#xff0c;Q输出端口的电压不会马上变化&#xff0c;原因是MOS管的寄生电容在充放电&#xff0c;该电路在…

设有一个由正整数组成的无序单链表,编写完成一以下功能的算法

题目 设有一个由正整数组成的无序单链表&#xff0c;实现以下功能1、找出最小的结点&#xff0c;并打印2、若该数值为奇数&#xff0c;则将其与直接后继结点的数值交换3、若该数值为偶数&#xff0c;则将其直接后继结点删除 代码 /** * 设有一个由正整数组成的无序单链表&am…

电脑突然开机无反应,怎么办

电脑常见故障之三开机无响应&#xff08;上&#xff09; 经常使用电脑的朋友应该会碰到这种情况&#xff0c;开机时按下电源按钮后&#xff0c;电脑无响应&#xff0c;显示器黑屏不亮。 除去那些傻瓜式的故障原因&#xff0c;如显示器、主机电源没插好&#xff1b;显示器与主…

如何实现三维虚拟数字人直播1:全身姿态预估

一、前言 业务需求:实现三维虚拟数字人直播 交互流程:【真人/视频】动作捕捉【面捕 + 动捕】 => 2D/3D数字人动作映射 =》场景可视化呈现 技术要点: 1. 动作捕捉: (1)面捕:Live Link (2)动捕:全身/手部/脚部 2.1 光学传感器:硬件设备 2.2 摄像头:单目摄像…

CSS :has伪类

CSS :has伪类1 概述2 实例说明2.1 表单元素前面加*2.2 拖拽列表2.3 多层级hover2.4 评星组件3 兼容性1 概述 :has()表示满足一定条件后&#xff0c;就会匹配该元素。这个伪类通过把可容错相对选择器列表作为参数&#xff0c;提供了一种针对引用元素选择父元素或者先前的兄弟元…

基于JavaWeb的电影网站的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

Vue项目实战——【基于 Vue3.x + NodeJS】实现的课程表排课系统三(duration-title)

文章目录【基于 Vue3.x NodeJS】实现的课程表排课系统三DurationTitle单元格点击策略模式渲染card测试一下做ScheduleCard做单元格内的【基于 Vue3.x NodeJS】实现的课程表排课系统三 优化一下左侧一列单元格&#xff08;显示时间的第一列&#xff09; 实现效果&#xff1a;…
最新文章