openpnp2.4 - 导入kicad9.0的封装数据

文章目录

openpnp2.4 - 导入kicad9.0的封装数据

概述

openpnp2.4中,在封装处,可以新建封装,然后自动导入kicad版本的元件封装文件(.kicad_mod). 这样就不用自己手工编辑元件焊盘数据了。

openpnp2.4只支持旧版的kicad. 我本本上装的是kicad9.0. 导入失败。

上图是我改过openpnp实现后的效果,改完了,初步测试了一下,好使。

openpnp导入kicad封装时,只能导入贴片焊盘和在铜皮上的焊盘,对于其他焊盘(通孔焊盘,机械孔)是无法导入的,但是对于openpnp贴片来说,导入贴片焊盘就够了(给视觉识别用)。

最近简单看了一下java swing编程,知道了UI界面的动作和动作处理函数之间的关系。剩下的和c++差不多。

如果要改openpnp代码,也容易多了。

上述修改,花了4个小时。

临时的修改,测试也不充分,远不到给openpnp贡献补丁的程度。自己临时用一下。

笔记

只修改了一个实现。

KicadModImporter.java

java 复制代码
/*
 * Copyright (C) 2023 Jason von Nieda <jason@vonnieda.org>
 * 
 * This file is part of OpenPnP.
 * 
 * OpenPnP is free software: you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * OpenPnP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with OpenPnP. If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * For more information about OpenPnP visit http://openpnp.org
 */

package org.openpnp.gui.importer;

import java.awt.FileDialog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.FileReader;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.openpnp.Translations;
import org.openpnp.gui.MainFrame;
import org.openpnp.model.Footprint;
import org.openpnp.model.Footprint.Pad;
import org.pmw.tinylog.Logger;

/**
 * @author Jonas Lehmke <jonas@lehmke.xyz>
 * 
 *         This module reads a KiCad footprint from file (*.kicad_mod) and
 *         parses it to a Footprint instance. Rectangular, rounded rectangular,
 *         circular and oval pad shapes are supported. Trapezoid and custom pad
 *         shapes are ignored. A FileDialog is openened to select a file once
 *         this class is instantiated. Imported pads are available as List<Pad>.
 * 
 *         This module may become part of an all-in-one KiCad board import one
 *         day.
 */

public class KicadModImporter {
    private boolean useLegacySingleLineParser = true; // default to legacy (single‑line) parser
    Footprint footprint = new Footprint();

    /**
     * Represents a pad in a Kicad footprint.
     * <p>
     * Example of Kicad 9.0 pad definition:
     * <pre>{@code
     * (pad "1" smd roundrect
     *      (at -4.175 -2.8)
     *      (size 1.5 0.5)
     *      (layers "F.Cu" "F.Mask" "F.Paste")
     *      (roundrect_rratio 0.25)
     * )
     * }</pre>
     */
    public class KicadPad {
        String padDefinition;

        public KicadPad(String definition) {
            padDefinition = definition;
        }

        String getName() {
            String regex;
            if (useLegacySingleLineParser) {
                regex = "^\\(pad\\s\"?(\\w*)\"?\\s(\\w*)\\s(\\w*)";
            } else {
                regex = "\\s*\\(pad\\s+\"?([^\"\\s]+)\"?\\s+(\\w+)(?:\\s+(\\w+))?";
            }
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return m.group(1);
            }
            return "";
        }

        String getType() {
            String regex;
            if (useLegacySingleLineParser) {
                regex = "^\\(pad\\s\"?(\\w*)\"?\\s(\\w*)\\s(\\w*)";
            } else {
                regex = "\\(pad\\s+\"?([^\"\\s]*)\"?\\s+(\\S+)\\s+(\\S+)";
            }

            Pattern p = Pattern.compile(regex);

            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return m.group(2);
            }
            return "";
        }

        String getShape() {
            String regex;
            if (useLegacySingleLineParser) {
                regex = "^\\(pad\\s\"?(\\w*)\"?\\s(\\w*)\\s(\\w*)";
            } else {
                regex = "\\s*\\(pad\\s+\"?(\\w*)\"?\\s+(\\w+)\\s+(\\w+)";
            }
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return m.group(3);
            }
            return "";
        }

        double getWidth() {
            String regex = "\\(size ([\\-0-9.]*) ([\\-0-9\\.]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {

                return Double.parseDouble(m.group(1));
            }
            return 0.;
        }

        double getHeight() {
            String regex = "\\(size ([\\-0-9\\.]*) ([\\-0-9\\.]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return Double.parseDouble(m.group(2));
            }
            return 0.;
        }

        double getX() {
            String regex = "\\(at ([\\-0-9.]*) ([\\-0-9.]*)\\s?([^\\)]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return Double.parseDouble(m.group(1));
            }
            return 0.;
        }

        double getY() {
            String regex = "\\(at ([\\-0-9.]*) ([\\-0-9.]*)\\s?([^\\)]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return Double.parseDouble(m.group(2)) * (-1); // Negative because of different conventions
            }
            return 0.;
        }

        double getRotation() {
            String regex = "\\(at ([\\-0-9.]*) ([\\-0-9.]*)\\s?([^\\)]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                if (m.group(3).length() > 0) {
                    return Double.parseDouble(m.group(3));
                }
            }
            return 0.;
        }

        double getRoundness() {
            String regex = "\\(roundrect_rratio ([\\-0-9.]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return Double.parseDouble(m.group(1)) * 100;
            }
            return 0.;
        }

        boolean isTopCu() {
            String regex = "\\(layers ([^\\)]*)\\)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(padDefinition);
            if (m.find()) {
                return m.group(1).contains("F.Cu") || m.group(1).contains("*.Cu");
            }
            return false;
        }
    }

    public KicadModImporter() throws Exception {
        try {
            FileDialog fileDialog = new FileDialog(MainFrame.get());
            fileDialog.setFilenameFilter(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".kicad_mod"); //$NON-NLS-1$
                }
            });
            fileDialog.setVisible(true);
            if (fileDialog.getFile() == null) {
                return;
            }

            File file = new File(new File(fileDialog.getDirectory()), fileDialog.getFile());

            // --- Detect KiCad version from the file ---
            try (BufferedReader versionReader = new BufferedReader(new FileReader(file))) {
                // Skip first line (footprint name)
                String line;
                while ((line = versionReader.readLine()) != null) {
                    String trimmed = line.trim();
                    if (trimmed.startsWith("(version ")) {
                        Pattern versionPattern = Pattern.compile("\\(version\\s+(\\d+)\\)");
                        Matcher m = versionPattern.matcher(trimmed);
                        if (m.find()) {
                            long version = Long.parseLong(m.group(1));
                            // KiCad 9.0 uses version 20241229 and later → use multiline parser
                            if (version >= 20241229) {
                                useLegacySingleLineParser = false;
                            }
                        }
                        break; // version found, no need to read further
                    }
                }
            } catch (Exception e) {
                // If version detection fails, fall back to legacy parser and print a warning
                Logger.error("Failed to detect KiCad version, using legacy parser. error : " + e.getMessage());
            }

            // --- Parse the footprint according to the detected version ---
            if (useLegacySingleLineParser) {
                // Legacy parser: assume each pad definition is on a single line
                try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        if (line.trim().startsWith("(pad ")) {
                            processPadDefinition(line.trim());
                        }
                    }
                }
            } else {
                // KiCad 9.0+ parser: pads may span multiple lines
                try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                    String line;
                    Logger.debug("Kicad Mod Importer - begin");
                    while ((line = reader.readLine()) != null) {
                        String trimmed = line.trim();
                        if (trimmed.startsWith("(pad ")) {
                            // Read the complete S‑expression for this pad
                            StringBuilder padBuilder = new StringBuilder(line);
                            int balance = countBrackets(line);
                            while (balance > 0) {
                                String nextLine = reader.readLine();
                                if (nextLine == null) {
                                    break; // Unexpected end of file -- pad is incomplete
                                }
                                padBuilder.append("\n").append(nextLine);
                                balance += countBrackets(nextLine);
                            }
                            String padDefinition = padBuilder.toString();
                            processPadDefinition(padDefinition);
                        }
                    }
                    Logger.debug("Kicad Mod Importer - end");
                }
            }
        } catch (Exception e) {
            throw new Exception(Translations.getString("KicadModImporter.LoadFile.Fail") + e.getMessage(), e); //$NON-NLS-1$
        }
    }

    /**
     * Counts the net parentheses in a line: '(' adds 1, ')' subtracts 1.
     */
    private int countBrackets(String line) {
        int count = 0;
        for (char c : line.toCharArray()) {
            if (c == '(')
                count++;
            else if (c == ')')
                count--;
        }
        return count;
    }

    /**
     * Processes a pad definition string (which may be single‑line or multi‑line)
     * and adds the corresponding Pad object to the footprint if it is an SMD pad
     * with a supported shape.
     */
    private void processPadDefinition(String padDefinition) {
        KicadPad kipad = new KicadPad(padDefinition);

        if (kipad.getType().equals("smd") && kipad.isTopCu()) {
            Pad pad = new Pad();
            pad.setName(kipad.getName());
            pad.setWidth(kipad.getWidth());
            pad.setHeight(kipad.getHeight());
            pad.setX(kipad.getX());
            pad.setY(kipad.getY());
            pad.setRotation(kipad.getRotation());

            String Shape = kipad.getShape();
            if (Shape.equals("rect")) {
                pad.setRoundness(0);
            } else if (Shape.equals("circle")) {
                pad.setRoundness(100);
            } else if (Shape.equals("oval")) {
                pad.setRoundness(100);
            } else if (Shape.equals("roundrect")) {
                pad.setRoundness(kipad.getRoundness());
            } else {
                Logger.warn("Warning: Unsupported pad type: " + kipad.getShape());
                return;
            }

            footprint.addPad(pad);
        } else {
            Logger.warn("Warning: [not smd pad] or [pad not on Front Copper], can't import pad [{}]", kipad.getName());
            return;
        }
    }

    public List<Pad> getPads() {
        return footprint.getPads();
    }
}

如何快速的找kicad提供的封装文件

kicad9.0安装后,自带封装库。位置 C:\Program Files\KiCad\9.0\share\kicad\footprints

封装文件的后缀为.kicad_mod

封装的命名规则 : https://klc.kicad.org/footprint/

kicad提供的封装很多,有15415个。封装的命名也很复杂。

如果按照官方按照功能分的文件夹,很难翻到自己想要的封装。

如果按照关键字,模糊的去搜索,反而能更快的找出来。

e.g. 0402封装的电阻

e.g. LQFP32的IC

e.g. SOP-8的IC

kicad中带的封装很全,对于同一类封装,可能有多个尺寸的封装,此时,比对一下自己用的元件数据表,就知道该用哪种封装了。

用kicad查看封装的大概样子

启动kicad后,选择封装编辑器

在封装编辑器中启动"封装库浏览器"

"封装库浏览器"中单击选中元件,就能看到元件封装的样子。

再改openpnp中的kicad封装导入时,如果哪个焊盘数据导入失败,且自己看不太懂.kicad_mod中描述的焊盘信息,对照封装编辑器中的焊盘需要和焊盘数据,就能很快明白了。

在封装编辑器中,双击焊盘,焊盘的属性就显示出来了,和.kicad_mod中自己不明白的焊盘数据对一下,就很清楚了。

END

相关推荐
LostSpeed1 天前
高精度juki吸嘴快拆连接器铜套座-v8 - 气密性验证
openpnp·juki吸嘴快拆连接器铜套座
LostSpeed6 天前
openpnp - 吸嘴校验失败的处理方法
openpnp
LostSpeed6 天前
openpnp - 测试片直径的选择 on “Calibrate precise camera ↔ nozzle N1 offsets“
openpnp
LostSpeed14 天前
openpnp - 调试环境搭建 - use eclipse for java - v2
openpnp
LostSpeed19 天前
openpnp - python2.7 script - 中文显示乱码,只能显示英文
python·openpnp
LostSpeed2 个月前
openpnp - Smoothieware - LPC17xx-DFU-Bootloader - 固件调试环境搭建
openpnp
LostSpeed2 个月前
openpnp - Smoothieware - MKS SGEN_L V1.0 + JLink-edu-mini 连接测试
openpnp·jlink·mks·smoothieware
LostSpeed2 个月前
openpnp - 相机模组调焦时,图像中心轴(光轴)的角度会发生轻微变化
openpnp
LostSpeed4 个月前
openpnp - 吸嘴的单独校准
openpnp