openpnp - convert allegro placement to named csv - v5

openpnp - convert allegro placement to named csv - v5

概述

手头的openpnp设备以前有问题,导致贴片精度差。
将设备以最高标准维护完,稳定性和丝杠重复精度都不错。
但是贴片精度还差。

记得厂家同学在我最近一次维护设备之前说:“按照你的想法将设备硬件升级完,如果贴片精度还差怎么办?”
我当时说,如果还有问题就再找呗。

本来也是,设备精度出了问题,那就一步一步的分析。
谁敢说,我一次就分析清楚,一次就搞定。
如果贴片精度仅仅由一个问题引起,将故障原因分析出来,那么升级了硬件之后,将这个问题搞定。那么精度就上来了。
但是这设备的问题很多,谁敢说这瓜包熟?
这设备的设计,装配都是厂家同学自己亲手弄的,设备有啥问题,厂家同学自己心里应该有哈数才对啊。
设备的作者都无法给出设备精度差的解决方向的有效建议,这有点过了。

现在已经确定硬件没问题了,但是贴片精度还差。
那可能原因就进一步缩小了,那么就怀疑坐标文件中的元件坐标不对。基本也不能是其他原因了。

因为allegro导出的坐标文件没办法给openpnp直接用。
我自己写了一个程序,将allegro的坐标文件转成了openpnp可用的named csv(cadence SPB17.4 - export placement file to openpnp).

首先,我检查了代码实现,没发现问题。

那进一步怀疑我从allegro导出placement时,导出选项勾错了。
allegro导出placement时,有3个选项:

  1. 按照符号原点来导出
  2. 按照body中心来导出
  3. 按照pin1的坐标来导出。

分别按照符号原点/body中心导出坐标文件,用我自己的工具转成openpnp能用的named csv之后,浏览全部元件,发现还是不准。
不是每个元件都不准,而是有些元件不准。

于是打开不准的元件封装,查看符号原点/body中心离理论上的元件中心的距离。
发现有些元件确实差很多,而且这些元件不是我自己弄的。而是从第三方扒下来的库,转成AD低版本,再用allegro导入,再导出为allegro的.dra封装放入自己的本地pcb_lib, 然后给工程用。

不可能选取符号中心来导出

如果按照符号原点来导出,这个符号原点是可以由封装设计者去人为设置的。
已经发现,有的封装的符号原点离理论上的元件中心差了有20个元件的长度的距离。
实际的例子如下:
在这里插入图片描述

所以,如果用allegro导出placement, 不可能选取符号中心来导出。

也不可能选取body中心来导出

如果是按照body中心来导出,这个doby中心也是封装设计者自己设定的。
body中心就是占地面积(元件放到pcb上),占用的平面空间。
如果在一脚放一个指示原点,那么占地面积就要将这个指示圆点包在占地面积里面。这就导致了元件引脚围成的实际元件几何中心和占地面积的中心有差异,这样贴片误差就产生了。
如果注意不到这一点,就会有贴片误差。
如果偏差少,可能95%的贴片操作者意识不到这个问题(因为表面看着元件还摆的挺好的)。但是这个偏差就可能会引起小元件立碑;引脚间距较密时,引起管脚连焊短路; 其他未知问题。

偏差的例子1 - 偏差小

在这里插入图片描述
红色的框框就是占地面积区,可以看到元件下方有一个1脚的指示原点。但是其他3面都是紧靠管脚的。
这样就导致,如果使用占地面积作为元件中心,就会导致X轴会有偏差。初步看起来,这个偏差就已经0.1mm了。

偏差的例子1 - 偏差大

贴片元件如果使用body中心为元件中心,如果占地面积和实际管脚围起来的几何图形不一样,那么贴片精度始终会差一点。
为了举一个偏差大的例子,我们来看一个插件的封装,假设如果有这样的贴片封装(假设插件管脚都换成贴片管脚)
在这里插入图片描述
如果上面这个插件的管脚都换成贴片管脚,又假设有这样的贴片元件存在。
这偏差就大了去了(至少要偏差2mm以上)。
红色区域的是占地面积(body), 可以看到占地面积的几何中心和由管脚围住的几何中心差的很多。

只能按照pin1来导出坐标文件

虽然符号原点和占地面积如果有问题,自己都可以修正。
但是时间成本花不起,尤其是如果已经有了一个历史库,如果要核对修改(符号原点和占地面积),搞死人啊。
特别是从第三方扒下来的封装,还要一个一个的核对修改?
还不算以后有新增的封装入库.

因为前2个导出选项都排除了,那么只能按照pin1来导出坐标文件。
导出后,虽然不在元件中心,但是可以用这个坐标文件来验证贴片机在平面(X-Y方向)上是否走的坐标精确?
试了一下,走的很准,只差5个丝(1丝 = 0.01mm).
这5个丝的误差是我设备的零件精度和装配精度的总误差。手工装配,总误差5个丝,已经满意了。

思路 - 如何将pin1规则导出的坐标文件转成openpnp可用的坐标文件?

我的上一版工具(cadence SPB17.4 - export placement file to openpnp)已经能正常处理按照allegro元件符号中心/body中心导出的坐标文件,转成openpnp可用的named csv文件。

我在上一版的基础上:

  1. 将pin1坐标的元件的封装(不管封装管脚如何摆放,封装对应的旋转角度都是0度),算出封装中心 pc(x, y).
  2. 坐标文件中的元件摆放角度为angle_part, 将元件封装的pin1, 沿着pc为中心,旋转angle_part的角度
  3. 计算旋转后的封装的pin1和pc之间的偏差poffset
  4. 将板子上的元件的pin1 + poffset, 就是元件在板子上的中心坐标。
    已经试过了,不管元件角度如何,用顶部相机浏览到元件时,元件中心都很准。产生的误差(e.g. 5个丝),误差和设备精度有关系,而不是坐标文件转换的问题。

如何算出元件的中心坐标?

元件只有1个管脚

那么这个管脚pin1就是元件中心,不需要修改

元件可以由2个管脚表示

e.g. 阻容 只有2个管脚
e.g. SOP-8 虽然有8个管脚,但是只要取pin1和pin5, 那么可以用2个管脚的中心当作8个管脚的中心
将2个pin的坐标(x, y)分别相加/2, 就是元件中点坐标

元件必须由3个管脚表示

e.g. SOT89-3, SOT23
另外,管脚多的元件(e.g. LQFP32), 为了精确算出中心坐标,也最好是用3个管脚来表示。
在这里插入图片描述
这3个管脚的选择规则
pin_s1管脚必须选择元件封装的1脚
pin_s2最好选择和pin1在一条边(X/Y方向的一条直线)上的一点。
pin_s3只用y坐标值
这样确定3点后,就可以用平面几何算出元件封装的几何中心点pc.
因为后面会贴实现,但是会略去如何算3个点的几何中心的实现(觉得这个函数自己写的还挺好的,自用了)。
所以这里只说大概思路,毕竟每个人的实现不一样。
如果按照平面几何的作图方法,步骤大概如下。
将pin_s1和pin_s2当作直线line12的起点和终点,那么可以算出line12的中点pt12.
在pt12上,做line12的垂直线line2
从pin_s3上做平行于line12的直线line3
在line2上,与line12和line3的交点,作为线段line_c.
那么line_c的中点,就是元件的几何图像(由管脚围成的几何图形,而不是由占地面积决定)的中心点pc, pc就是我们算出的元件理论的中心点。

用顶部相机浏览的效果

在用openpnp浏览载入的坐标文件前,一定要按照openpnp的校验流程,将设备标定完。此时,就可以由openpnp控制,消去设备零件的安装误差。
在这里插入图片描述
在这里插入图片描述
从有些元件上看,自己算出的精确元件中心和body中心差的挺多的。e.g. 上面这个SOT89-3
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
上面这个元件看着X方向有点偏差,但是用顶部相机标尺量一下就知道,偏差只有5个丝。这是设备的总误差,忽略。
现在设备的精度误差已经小于0.1mm了(5个丝),俺已经很满意了(CNC出来的零件,默认公差是+/-0.1mm(+/-10个丝))。
在这里插入图片描述
在这里插入图片描述
上面这个元件,可以看出,自己算出的元件精确几何中心(由元件管脚围成的几何图形)和占地面积的中心还是差很多的。

笔记

编程环境

VS2019社区版
如果没有VS2019社区版,可以参照下面2个笔记,下载VS2019社区版
VS - download VS2019 community from MS site
制作VS2019Community的离线安装包

工程结构

在这里插入图片描述
GeomCore.cpp 我自用了,没在下面放出来。
思路上面已经说了,大家自己实现即可。

需要您自己实现的函数

如果您也遇到这个由allegro导出坐标文件的精度引起的贴片精度问题。
如果您想从头解决这个问题,估计您要写几天。

如果您在我的这个工程基础上解决,大概1个小时(主要就是填GeomCore.h中的4个函数).
这个工程中,如何算出元件的中心点,需要您自己来实现.

GeomCore.h

请实现GeomCore.h声明的4个函数到GeomCore.cpp中。

mm2mil();
mil2mm();
cpt_2pt_mid_pt();
cpt_3pt_mid_pt();
//! @file GeomCore.h
//! @brief 几何运算核心库

#ifndef GEOMCORE_H
#define GEOMCORE_H

/**
    @struct _tag_point_double
    @brief 坐标点
**/
typedef struct _tag_point_double {
    int pin_number; // 引脚号码. e.g. pin1 这里就填1, pin10就填10, 对于元件,管脚号码是从1开始的。

    // 元件引脚坐标是用Allegro Library Creator 17.4来查看的, 单位都是mil
    double pos_x; // pin的X坐标, 单位是mil
    double pos_y; // pin的Y坐标, 单位是mil
}TAG_POINT_DOUBLE;

/**
    @struct _tag_part_footprint_info
    @brief 封装信息
**/
typedef struct _tag_part_footprint_info {
    bool is_valid; // 数组中的最后一个结构, is_valid = false, 就不判断了
    bool is_unit_mil; // 坐标单位是否为mil? 用allegro做的元件,大部分单位都是mil. 但是从第三方扒下来的封装,有的是mm单位
    double fAngle; // 封装的角度,恒为0度,没用。

    const char* pszFootprintName; // 封装名称
    TAG_POINT_DOUBLE pin1; // pin1的坐标

    // iCtrlPtCnt必须>=2, 其中 pin1算是一个控制引脚
    // 其他的控制引脚是几个,由iCtrlPtCnt来控制
    // 如果 (iCtrlPtCnt == 2), 就由pin1, pin_ctrl2来算中心点坐标
    // 如果 (iCtrlPtCnt == 3), 就由pin1, pin_ctrl2, pin_ctrl3来算中心点坐标
    int iCtrlPtCnt; // 除了pin1之外的控制点坐标数量(e.g. C1206, 再给出一个pin2的坐标,就可以算出中心坐标)

    // 除了pin1, 其他的控制引脚,最多2个就行。
    // 对于偶数引脚数目的元件(e.g. SOP-8),只要给出pin1的一个对角坐标就能算出元件中心点
    // 对于奇数引脚数目的元件(e.g. SOT23), 只要给出和pin1一边的引脚坐标,再给出另外一边的一个中心点pin的坐标就行
    TAG_POINT_DOUBLE pin_ctrl2;
    TAG_POINT_DOUBLE pin_ctrl3;
}TAG_PART_FOOTPRINT_INFO;

/**
 * @fn double mm2mil(double fmm);
 * @brief mm转mil
 */
double mm2mil(double fmm);

/**
 * @fn double mil2mm(double fMil);
 * @brief mil转mm
 */
double mil2mm(double fMil);

/**
 * @fn bool cpt_2pt_mid_pt(double fAngle_part, double fAngle_footprint, bool isMil, TAG_POINT_DOUBLE& pt1, TAG_POINT_DOUBLE& pt2, TAG_POINT_DOUBLE& pt_calc);
 * @brief 算出由2个控制点决定的板子上实际元件的pin1到元件中心的偏移
 * @param double fAngle_part, 元件在板子上实际要摆放的角度(allegro导出的坐标文件中有)
 * @param double fAngle_footprint, 元件在封装库中的角度(恒为0), 不用理会
 * @param bool isMil, allegro坐标文件中的值是否为mil, true = 是mil, false = 不是mil, 是mm
 * @param TAG_POINT_DOUBLE& pt1, 封装中您选择的第1个脚坐标(必须是pin1)
 * @param TAG_POINT_DOUBLE& pt2, 封装中您选择的第2个引脚坐标(一般就是最后一个引脚; 如果是2脚的阻容,就是pin2; 如果是多脚的SOP-8, 就是pin1的对角pin5)
 * @param TAG_POINT_DOUBLE& pt_calc, 出参, 由pt1连接到pt2的直线的中点坐标,也就是这个2脚能控制的元件的中心坐标
 * @return bool
 * @retval true, 函数执行成功
 * @retval false, 函数执行失败
 * @note 没有能返回false的可能,是直线逻辑, 没有可能失败的分差逻辑。
 */
bool cpt_2pt_mid_pt(double fAngle_part, double fAngle_footprint, bool isMil, TAG_POINT_DOUBLE& pt1, TAG_POINT_DOUBLE& pt2, TAG_POINT_DOUBLE& pt_calc);

/**
 * @fn bool cpt_3pt_mid_pt(double fAngle_part, double fAngle_footprint, bool isMil, TAG_POINT_DOUBLE& pt1, TAG_POINT_DOUBLE& pt2, TAG_POINT_DOUBLE& pt3, TAG_POINT_DOUBLE& pt_calc);
 * @brief 算出由3个控制点决定的板子上的实际元件的pin1到元件中心的偏移
 * @param double fAngle_part, 元件在板子上实际要摆放的角度(allegro导出的坐标文件中有)
 * @param double fAngle_footprint, 元件在封装库中的角度(恒为0), 不用理会
 * @param bool isMil, allegro坐标文件中的值是否为mil, true = 是mil, false = 不是mil, 是mm
 * @param TAG_POINT_DOUBLE& pt1, 封装中您选择的第1个脚坐标(必须是pin1)
 * @param TAG_POINT_DOUBLE& pt2, 封装中您选择的第2个引脚坐标(一般是和pin1在一边的一个管脚. e.g. SOP-8的除了pin1之外的管脚, 最好是pin4(这一边的最后一个管脚))
 * @param TAG_POINT_DOUBLE& pt3, 封装中您选择的第3个引脚坐标(一般是对边的一个管脚, 用来决定元件宽度. e.g. SOP-8的pin5~pin8都行, SOT23的对边的那个单独管脚)
 * @param TAG_POINT_DOUBLE& pt_calc, 出参, 由pt1连接到pt2的直线和pt3给定的元件高度的中心,也就是这个3脚能控制的元件的中心坐标
 * @return bool
 * @retval true, 函数执行成功
 * @retval false, 函数执行失败
 * @note 没有能返回false的可能,是直线逻辑, 没有可能失败的分差逻辑。
 */
bool cpt_3pt_mid_pt(double fAngle_part, double fAngle_footprint, bool isMil, TAG_POINT_DOUBLE& pt1, TAG_POINT_DOUBLE& pt2, TAG_POINT_DOUBLE& pt3, TAG_POINT_DOUBLE& pt_calc);

#endif // #ifndef __GEOMCORE_H__

需要您更换自己的元件封装信息

因为每个人用的PCB库都不一样(封装名称,封装管脚坐标),您可以修改 part_footprint_info_data.cpp 中的 g_part_info(删掉我自用库的封装信息,换上您自己封装库中的元件封装信息)

程序用法

在这里插入图片描述

@echo off
rem run.cmd
rem openpnp_named_cvs_convert.exe 是本工程编译好的工具
rem place_pin_one.txt 是从allegro按照pin1规则导出的坐标文件
rem BOM.csv 是从原理图导出的BOM表文件, 导出方法请参阅 https://lostspeed.blog.csdn.net/article/details/127820026
call openpnp_named_cvs_convert.exe place_pin_one.txt BOM.csv
pause 

BOM.csv的样例

Item Number,Quantity,Value,Value_BOM,Description,Part Reference,Part_Number
1,1,PKM13EPYH4000-A0,PKM13EPYH4000-A0,PKM13EPYH4000-A0 无源 直插 工业级,B1,BZ_0001
2,4,1uF/10%/100V/1206,1uF/±10%/100V/1206,贴片电容(MLCC) 1uF ±10% 100V 1206,C1 C3 C16 C17,CAP_0010
3,1,100nF/10%/100V/1206,100nF/±10%/100V/1206,贴片电容(MLCC) 100nF ±10% 100V 1206,C2,CAP_0015
4,1,10uF/10%/25V/1206,10uF/±10%/25V/1206,贴片电容(MLCC) 10uF ±10% 25V 1206,C4,CAP_0012

place_pin_one.txt的样例

UUNITS = MILS
C2                   446.55        2310.19       90    C1206               
B1                   1142.51       2592.62       90    BUZ-TH_BD12_6-P5_00-D0_5
C1                   542.29        2310.79       90    C1206               
C16                  2504.78       1534.52        0    C1206               

工程实现

openpnp_named_cvs_convert.cpp

// @file openpnp_named_cvs_convert.cpp
// @brief 转换SPB17.4 allegro导出的坐标文件(新格式) + orcad BOM单 转换为openpnp可导入的坐标文件
// @note 编译环境 vs2022 vc++ console

#include "sys_lib.h"

#include "GeomCore.h"
#include "part_opt.h"
#include "strOpt.h"

#define OPENPNP_NAMED_CSV_FILE "openpnp_named_csv.csv"
#define APP_VER "openpnp_named_cvs_convert v2025-0301 14:00"

// 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);
/**
    @brief 
    @param pszSrc - 
    @param c_find - 
    @param c_new  - 
**/
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);

/**
    @brief 
    @param pszAllegroFile    - 
    @param pszOrcadBomFile   - 
    @param pszOpenpnpCsvFile - 
**/
void process_allegro_csv(const char* pszAllegroFile, const char* pszOrcadBomFile, const char* pszOpenpnpCsvFile);

/**
    @brief 
    @param pFile    - 
    @param fpFile   - 
    @param pFileNew - 
**/
void process_allegro_csv(const char* pFile, FILE* fpFile, const char* pFileNew);

/**
    @brief 
    @param pFile      - 
    @param fpFile     - 
    @param pFileNew   - 
    @param fpFileNew  - 
    @param bUnitFind  - 
    @param bUnitIsMm  - 
    @param bUnitIsMil - 
**/
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_posX, char* sz_buf_field3_posY, char* sz_buf_field4_angle, char* sz_buf_field5, char* sz_buf_field6_footprint_name);

/**
    @brief  
    @param  pszOrcadBomFile - 
    @retval                 - 
**/
bool process_orcad_bom_file(const char* pszOrcadBomFile);

bool b_debug_flag = false;
int main(int argc, char** argv)
{
    const char* pszAllegroFile = NULL; // allegro 坐标文件
    const char* pszOrcadBomFile = NULL; // orcad 料单
    const char* pszOpenpnpNamedCsvFile = NULL; // 转换完成后的openpnp坐标文件

    std::cout << APP_VER << "\r\n";
    std::cout << std::fixed << std::setprecision(3);

    do {
        if ((3 != argc) && (4 != argc))
        {
            // 测试用的 allegro导出的原始坐标文件为 allegro_org.txt
            printf("请给出要转换的csv文件名称\r\n");
            
            printf("用法[1] : openpnp_named_cvs_convert allegro_org.txt BOM_orcad_org.csv\r\n");
            printf("一共给了2个参数\r\n");
            printf("参数    1: allegro_org.txt 是allegro导出的坐标文件\r\n");
            printf("参数    2: BOM_orcad_org.csv 是orcad导出的料单\r\n");
            printf("默认参数3: openpnp_named_csv.csv 是转换后的openpnp可导入的最终坐标文件\r\n");

            printf("\r\n");

            printf("用法[2] : openpnp_named_cvs_convert allegro_org.txt BOM_orcad_org.csv openpnp_named_csv.csv\r\n");
            printf("一共给了3个参数\r\n");
            printf("参数1: allegro_org.txt 是allegro导出的坐标文件\r\n");
            printf("参数2: BOM_orcad_org.csv 是orcad导出的料单\r\n");
            printf("参数3: openpnp_named_csv.csv 是转换后的openpnp可导入的最终坐标文件\r\n");

            printf("参数2(allegro导出的坐标文件)必须用pin1规则导出");
            
            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);

        if (3 == argc)
        {
            pszOpenpnpNamedCsvFile = OPENPNP_NAMED_CSV_FILE;
        }
        else if (4 == argc)
        {
            pszOpenpnpNamedCsvFile = argv[3];
        }
        else {
            assert(false);
        }

        printf("预期转换完的openpnp坐标文件为 : %s\r\n", pszOpenpnpNamedCsvFile);

        process_allegro_csv(pszAllegroFile, pszOrcadBomFile, pszOpenpnpNamedCsvFile);
        printf("处理结束 : 请查看转换完的openpnp坐标文件 %s 是否处理正确\r\n", pszOpenpnpNamedCsvFile);
    } 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;
            }

            b_debug_flag = true;
            process_convert_body(fpFileNew, bUnitIsMm, sz_buf_field1, sz_buf_field2, sz_buf_field3, sz_buf_field4, sz_buf_field5, sz_buf_field6);
            b_debug_flag = false;
        }
    } 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_posX, char* sz_buf_field3_posY, char* sz_buf_field4_angle, char* sz_buf_field5, char* sz_buf_field6_footprint_name)
{
    // 原始
    // "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 fTmpX = 0;
    double fTmpY = 0;
    double fX = 0;
    double fY = 0;
    double fAngle_part = 0;
    char* psz_tmp = NULL;
    bool bFixPosOk = false;

    // 从allegro按照pin1规则导出的坐标文件, 默认单位就是mil
    // 我处理的时候,也是将坐标单位值作为mil开处理。
    MY_ASSERT((!bUnitIsMm), "坐标文件单位必须是mil");

    // sz_buf_field1 is J4

    // sz_buf_field4 is 270
    fAngle_part = atof(sz_buf_field4_angle);

    // sz_buf_field2 is 157.00
    fTmpX = atof(sz_buf_field2_posX);
    
    // sz_buf_field3 is 899.00
    fTmpY = atof(sz_buf_field3_posY);

    if (my_cmp_string_nocase("C1206", sz_buf_field6_footprint_name))
    {
        // debug only
        fX = fX;
    }
    else {
    }

    bFixPosOk = fix_posXY(sz_buf_field6_footprint_name, fAngle_part, fTmpX, fTmpY, !bUnitIsMm, fX, fY);
    if (!bFixPosOk)
    {
        // for debug only
        std::cout << "fix_posXY failed : footprint = " << sz_buf_field6_footprint_name << "\r\n";
        bFixPosOk = bFixPosOk;
    }

    // 如果坐标转换失败报断言,就要去修改程序,将封装加入登记表
    MY_ASSERT(bFixPosOk, "failed fix_posXY, footprint is [%s]", sz_buf_field6_footprint_name);

    // 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_part,
        sz_buf_field5_new,
        sz_buf_field6_footprint_name,
        sz_buf_field7_new
        );

    fwrite(sz_buf_write, sizeof(char), strlen(sz_buf_write), fpFileNew);
}

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;
        }
    }
}

sys_lib.h

//! @file sys_lib.h
//! @brief 所有要包含的系统库的头文件


#ifndef SYS_LIB_H
#define SYS_LIB_H

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

#include <cmath> // for M_PI // 需要定义宏 _USE_MATH_DEFINES
#include <cstdio> 
#include <cstdlib> // 用于 abort() 或 exit() 
#include <cassert> 
#include <list>
#include <string>
#include <iostream>
#include <iomanip> // for std::fixed, std::setprecision

// 打印文件错误行信息
#if defined(NDEBUG)
#define MY_ASSERT_FILE_LINE() \
do { \
    fprintf(stderr, "[%s] %s:%d\r\n", "FATAL", __FILE__, __LINE__); \
    std::abort(); \
} while (0)
#elif defined(_DEBUG)
#define MY_ASSERT_FILE_LINE() \
do { \
    assert(false); \
} while (0)
#endif

// 自定义断言
#define MY_ASSERT(expr, fmt, ...) \
do { \
    if (!(expr)) { \
        fprintf(stderr, fmt, ##__VA_ARGS__); \
        fprintf(stderr, " | "); \
        MY_ASSERT_FILE_LINE(); \
    } \
} while (0)

#endif // #ifndef SYS_LIB_H

strOpt.h

/**
 * @file strOpt.h
 */

#ifndef STR_OPT_H
#define STR_OPT_H


bool my_cmp_string_nocase(const char* pA, const char* pB);

#endif // #ifndef STR_OPT_H

strOpt.cpp

//! @file strOpt.cpp

#include "sys_lib.h"
#include "strOpt.h"

bool my_cmp_string_nocase(const char* pA, const char* pB) {
    if (!pA || !pB) return false;
    return (0 == _stricmp(pA, pB));
}

part_opt.h

//! @file part_opt.h
//! @brief 元件操作

#ifndef PART_OPT_H
#define PART_OPT_H

#include "GeomCore.h"


bool find_part_info(const char* pszFootPrintName, TAG_PART_FOOTPRINT_INFO& part_info);
bool fix_posXY(const char* pszFootPrintName, double fAngle_part, double fMilX, double fMilY, bool bIsMil, double& fmmX, double& fmmY);

#endif // #ifndef PART_OPT_H

part_opt.cpp

//! part_opt.cpp

#include "sys_lib.h"

#include "part_opt.h"
#include "part_footprint_info_data.h"
#include "strOpt.h"
/**
    @brief  
    @param  pszFootPrintName - 
    @param  part_info        - 
    @retval                  - 
**/
bool find_part_info(const char* pszFootPrintName, TAG_PART_FOOTPRINT_INFO& part_info)
{
    bool b_rc = false;
    int i_ary_index = 0;

    do {
        if (NULL == pszFootPrintName)
        {
            break;
        }

        do {
            if (!g_part_info[i_ary_index].is_valid)
            {
                goto END;
            }

            if (my_cmp_string_nocase(pszFootPrintName, g_part_info[i_ary_index].pszFootprintName))
            {
                // find it

                // fill to out para
                part_info = g_part_info[i_ary_index]; // 浅拷贝即可

                b_rc = true;
                goto END;
            }

            i_ary_index++;
        } while (true);
    } while (false);

END:
    return b_rc;
}

bool fix_posXY(const char* pszFootPrintName, double fAngle_part, double fMilX, double fMilY, bool bIsMil, double& fmmX, double& fmmY)
{
    bool b_rc = false;
    TAG_PART_FOOTPRINT_INFO part_info;
    TAG_POINT_DOUBLE pt_calc;
    double fx = 0;
    double fy = 0;

    do {
        if (!find_part_info(pszFootPrintName, part_info))
        {
            break;
        }

        if (1 == part_info.iCtrlPtCnt)
        {
            // 一个pin的元件, 坐标不变, 元件的中心坐标,就是pin1的坐标
            fx = fMilX;
            fy = fMilY;

            fmmX = mil2mm(fx);
            fmmY = mil2mm(fy);

            b_rc = true;
        }
        else if (2 == part_info.iCtrlPtCnt)
        {
            // 2个控制点的实现
            if (!cpt_2pt_mid_pt(fAngle_part, part_info.fAngle, part_info.is_unit_mil, part_info.pin1, part_info.pin_ctrl2, pt_calc))
            {
                break;
            }

            fx = fMilX + pt_calc.pos_x;
            fy = fMilY + pt_calc.pos_y;

            fmmX = mil2mm(fx);
            fmmY = mil2mm(fy);

            b_rc = true;
        }
        else if (3 == part_info.iCtrlPtCnt)
        {
            // 3个控制点的实现
            if (!cpt_3pt_mid_pt(fAngle_part, part_info.fAngle, part_info.is_unit_mil, part_info.pin1, part_info.pin_ctrl2, part_info.pin_ctrl3, pt_calc))
            {
                break;
            }

            fx = fMilX + pt_calc.pos_x;
            fy = fMilY + pt_calc.pos_y;

            fmmX = mil2mm(fx);
            fmmY = mil2mm(fy);

            b_rc = true;
        }
        else {
            MY_ASSERT(false, "只支持1,2,3个控制点");
        }
    } while (false);

    // 出函数的时候,如果计算的坐标是对的,必须转成mm值给openpnp用。
    return b_rc;
}

part_footprint_info_data.h

//! @file part_footprint_info_data.h
//! @brief 元件封装信息的结构数组定义

#ifndef PART_FOOTPRINT_INFO_DATA_H
#define PART_FOOTPRINT_INFO_DATA_H

#include "GeomCore.h"

extern TAG_PART_FOOTPRINT_INFO g_part_info[];

#endif // #ifndef PART_FOOTPRINT_INFO_DATA_H

part_footprint_info_data.cpp

//! @file part_footprint_info_data.cpp

#include "part_footprint_info_data.h"
#include "sys_lib.h"


/**
 * @note 元件库中的元件封装(.dra), 无论摆成什么样子,封装的角度都为0度
 * 只要allegro的PCB中摆放的元件和.dra中看到的一样,该元件的摆放角度就是0度
 * allegro的PCB中, 元件的0角度指向右边,逆时针旋转为+角度
 * openpnp中元件的0角度指向上边,逆时针选装为+角度
 */
TAG_PART_FOOTPRINT_INFO g_part_info[] = {

    // C1206
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "C1206", // 封装名称
    TAG_POINT_DOUBLE{1, -62.7, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 62.7, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // F1206
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "F1206", // 封装名称
    TAG_POINT_DOUBLE{1, -56.9, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 56.9, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // BUZ-TH_BD12_6-P5_00-D0_5
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "BUZ-TH_BD12_6-P5_00-D0_5", // 封装名称
    TAG_POINT_DOUBLE{1, -98.4, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 98.4, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // C0603
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "C0603", // 封装名称
    TAG_POINT_DOUBLE{1, -27.6, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 27.6, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // C0805
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "C0805", // 封装名称
    TAG_POINT_DOUBLE{1, -40.6, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 40.6, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // SMA_L4_4-W2_6-LS5_0-BI
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "SMA_L4_4-W2_6-LS5_0-BI", // 封装名称
    TAG_POINT_DOUBLE{1, -78.7, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 78.7, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // SMA_L4_2-W2_7-LS5_0-BI
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "SMA_L4_2-W2_7-LS5_0-BI", // 封装名称
    TAG_POINT_DOUBLE{1, -78.7, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 78.7, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // DW_HOLE_D2R0MM_D3R0MM
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "DW_HOLE_D2R0MM_D3R0MM", // 封装名称
    TAG_POINT_DOUBLE{1, 0, 0}, // pin1坐标(pin_number, posx, posy)
    1, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 0, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // HDR-TH_3P-P2_54-V-M
    // 这是一个3pin的元件,但是是一排的,pin2正好在pin1,3中间。
    // 可以当作2pin的元件来算中心
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "HDR-TH_3P-P2_54-V-M", // 封装名称
    TAG_POINT_DOUBLE{1, 2978.1, 2974.1}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{3, 3178.1, 2974.1}, // 控制点2
    TAG_POINT_DOUBLE{2, 0, 0} // 控制点3
    },

    // HDR-TH_5P-P2_54-V-M
    // 可以当作2pin的元件来算中心
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "HDR-TH_5P-P2_54-V-M", // 封装名称
    TAG_POINT_DOUBLE{1, -200.0, 0}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{5, 200.0, 0}, // 控制点2
    TAG_POINT_DOUBLE{2, 0, 0} // 控制点3
    },

    // KRGY_KF142V_5D08_4P
    // 这是一个8脚的元件(2 x 4 pin), 可以取2个对角的pin, 当作2脚的元件来算中心
    // 这是一个插件,不用贴,但是坐标文件里面有,也处理一下。等浏览板子上元件时,也可以看一下元件中心是否正确
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    false, // 坐标单位是否为mil?
    0, // 封装的角度
    "KRGY_KF142V_5D08_4P", // 封装名称
    TAG_POINT_DOUBLE{1, -9.120, -5.550}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{5, 6.120, 2.070}, // 控制点2
    TAG_POINT_DOUBLE{2, 0, 0} // 控制点3
    },

    // LED-TH_BD3_8-P2_54-FD_WHITE
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    false, // 坐标单位是否为mil?
    0, // 封装的角度
    "LED-TH_BD3_8-P2_54-FD_WHITE", // 封装名称
    TAG_POINT_DOUBLE{1, -49.8, -0.1}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 50.2, -0.1}, // 控制点2
    TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // IND-SMD_4P-L4_5-W3_2-TL
    // 4pin的晶振,但是封装中是旋转90度的
    // 其实我只想算元件中心的坐标,所以和旋转角度无关。
    // 但是,如果元件管脚围成的几何图形不是完全对称的(e.g. 长方形)
    // 那么pin1的坐标和中心的偏移就和元件的旋转角度有关系了
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "IND-SMD_4P-L4_5-W3_2-TL", // 封装名称
    TAG_POINT_DOUBLE{1, -89.8, 49.2}, // pin1坐标(pin_number, posx, posy)
    2, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{3, 89.8, -49.2}, // 控制点2
    TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // LCD_YR91050A_V1
        TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        false, // 坐标单位是否为mil?
        0, // 封装的角度
        "LCD_YR91050A_V1", // 封装名称
        TAG_POINT_DOUBLE{1, -30.480, 0}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{13, 0, 0}, // 控制点2
        TAG_POINT_DOUBLE{14, -5.080, 55.000} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // MY_MARK_POINT_SMT_D1MM
    TAG_PART_FOOTPRINT_INFO{
    true, // 是否有效
    false, // 坐标单位是否为mil?
    0, // 封装的角度
    "MY_MARK_POINT_SMT_D1MM", // 封装名称
    TAG_POINT_DOUBLE{1, 0, 0}, // pin1坐标(pin_number, posx, posy)
    1, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 0, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3
    },

    // SOT-23_L2_9-W1_3-P1_90-LS2_4-BR
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SOT-23_L2_9-W1_3-P1_90-LS2_4-BR", // 封装名称
        TAG_POINT_DOUBLE{1, 45.3, -37.4}, // pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 45.3, 37.4}, // 控制点2
        TAG_POINT_DOUBLE{3, -45.3, 0} // 控制点3
    },

    // R0603
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "R0603", // 封装名称
        TAG_POINT_DOUBLE{1, -29.7, 0}, // pin1坐标(pin_number, posx, posy)
        2, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 29.7, 0}, // 控制点2
        TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // R0805
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "R0805", // 封装名称
        TAG_POINT_DOUBLE{1, -28.0, 0}, // pin1坐标(pin_number, posx, posy)
        2, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 50.7, 0}, // 控制点2
        TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // SW-TH_4P-L6_0-W6_0-P4_50-LS6_5
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SW-TH_4P-L6_0-W6_0-P4_50-LS6_5", // 封装名称
        TAG_POINT_DOUBLE{1, -128.0, 88.6}, // pin1坐标(pin_number, posx, posy)
        2, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{4, 128.0, -88.6}, // 控制点2
        TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // TEST_PT_TH
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        false, // 坐标单位是否为mil?
        0, // 封装的角度
        "TEST_PT_TH", // 封装名称
        TAG_POINT_DOUBLE{1, 0, 0}, // pin1坐标(pin_number, posx, posy)
        1, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 0, 0}, // 控制点2
        TAG_POINT_DOUBLE{0, 0, 0} // 控制点3
    },

    // SOT-89-3_L4_5-W2_5-P1_50-LS4_2-BR
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SOT-89-3_L4_5-W2_5-P1_50-LS4_2-BR", // 封装名称
        TAG_POINT_DOUBLE{1, 53.4, -59.1}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{3, 53.4, 59.1}, // 控制点2
        TAG_POINT_DOUBLE{2, -53.4, 0} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // SOT-23-3L_L2_9-W1_6-P1_90-LS2_8-BL
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SOT-23-3L_L2_9-W1_6-P1_90-LS2_8-BL", // 封装名称
        TAG_POINT_DOUBLE{1, -37.4, -49.8}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 37.4, -49.8}, // 控制点2
        TAG_POINT_DOUBLE{3, 0.2, 49.8} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // LQFP-32_L7_0-W7_0-P0_80-LS9_0-BL
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "LQFP-32_L7_0-W7_0-P0_80-LS9_0-BL", // 封装名称
        TAG_POINT_DOUBLE{1, -110.2, -167.0}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{8, 110.2, -167.0}, // 控制点2
        TAG_POINT_DOUBLE{18, 78.7, 167.0} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // SSOP-48_L16_0-W7_6-P0_64-LS10_7-BL
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SSOP-48_L16_0-W7_6-P0_64-LS10_7-BL", // 封装名称
        TAG_POINT_DOUBLE{1, -284.7, -183.9}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{24, 290.3, -183.9}, // 控制点2
        TAG_POINT_DOUBLE{25, 290.3, 184.4} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // AHT20_AHT20
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        false, // 坐标单位是否为mil?
        0, // 封装的角度
        "AHT20_AHT20", // 封装名称
        TAG_POINT_DOUBLE{1, -1, 1}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{3, -1, -1}, // 控制点2
        TAG_POINT_DOUBLE{4, 1, -1} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // SOP-8_L5_0-W4_0-P1_27-LS6_0-BL
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SOP-8_L5_0-W4_0-P1_27-LS6_0-BL", // 封装名称
        TAG_POINT_DOUBLE{1, -75, -109}, //  pin1坐标(pin_number, posx, posy)
        3, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{4, 75, -109}, // 控制点2
        TAG_POINT_DOUBLE{5, 75, 109} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    // SOD-323_L1_8-W1_3-LS2_5-RD
    TAG_PART_FOOTPRINT_INFO{
        true, // 是否有效
        true, // 坐标单位是否为mil?
        0, // 封装的角度
        "SOD-323_L1_8-W1_3-LS2_5-RD", // 封装名称
        TAG_POINT_DOUBLE{1, -46.2, 0}, //  pin1坐标(pin_number, posx, posy)
        2, // 控制点总数量(包含pin1)
        TAG_POINT_DOUBLE{2, 46.2, 0}, // 控制点2
        TAG_POINT_DOUBLE{0, 0, 0} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },

    

    // 结尾的part, 不参与判断
    TAG_PART_FOOTPRINT_INFO{
    false, // 是否有效
    true, // 坐标单位是否为mil?
    0, // 封装的角度
    "", // 封装名称
    TAG_POINT_DOUBLE{1, 0, 0}, //  pin1坐标(pin_number, posx, posy)
    3, // 控制点总数量(包含pin1)
    TAG_POINT_DOUBLE{2, 0, 0}, // 控制点2
    TAG_POINT_DOUBLE{3, 0, 0} // 控制点3 // 只用来提供相对于pt1, pt2所在直线的高度信息
    },
};

备注

现在硬件没问题了,解决了allegro placement to openpnp named csv。
下一步就可以试贴了。
从设备调试和用openpnp浏览named csv的过程中,设备的表现来看。好像是搞定贴片精度问题了。
这么巨大的一个坑被我硬生生的填上了。

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值