RSS Twitter Facebook

2018/09/30 (2018年09月 のアーカイブ)

KiCad : Pythonスクリプトでガーバーをまとめる

今年 7 月に基板 CAD の KiCAD が 1 年ぶりくらいに 5.0 にバージョンアップして以来、はじめてちゃんと使ってみたのですが、なかなか良いです。レンダリングを OpenGL にすると動かない機能があるとかいう中途半端な感じがなくなってるし、ライブラリもかなり整備されました。個人的にはワイヤーを削除した時にジャンクションだけが取り残されたりしなくなったのでフラストレーションが激減しました。

KiCad

それでもうひとつ気になる機能があって Action Plugins という奴なんですが、これは Python で書いたプラグインでメニューからマクロ操作的な事ができるというもので 5.0 から使える予定だったのですが、これの動かし方がわからない。

という事で色々調べた結果、今公開されている 5.0.0 では何かの手違いで無効にしたまま公開してしまったようです。そのうちアップデートされると思いますが、今すぐ使ってみるには安定版の 5.0.0 ではなくて Nightly Build の方を入れれば試せます。

以前のバージョンでも Python スクリプト自体は使えたのですが、呼び出し方にいまいち問題があって、Python コンソールを開いてコマンド名を打ち込む必要がありました。これだと、ちょっとした便利コマンドみたいなものをせっかく作っても、いざ使おうとした時にはもう使い方を覚えていないとか、存在そのものを覚えていないという事になりがちです。 で、これをメニューに登録できるようにした、とまあそれだけの事ではあるんですけど、使い勝手はかなり違うと思います。



ただこの Python スクリプト周りはまだドキュメンテーションが行き届いていないので、どうやればやりたい事ができるのか調べるのには結構手探りの試行錯誤が必要そうです。とりあえず、ガーバーファイルを FusionPCB とか Elecrow スタイルにリネームして ZIP にまとめるスクリプトを書いてみました。

動かすには Linux の場合、http://docs.kicad-pcb.org/doxygen/md_Documentation_development_pcbnew-plugins.html にあるように、このファイル ( action_menu_gerber_zip.py ) を /usr/share/kicad/scripting/plugins/ あたりに置けば動くようです。ただし Windows の場合はこの説明が怪しくて色々やった所どうやら、

C:\Users\ユーザー名\AppData\Roaming\kicad\scripting

に置けば動作するようです(このあたりの挙動はまだ確定かどうかわからない)。

スクリプトの中身ですが、FusionPCB や Elecrow を使っている人なら何度もやる事になるガーバーをリネームしてZIPで固めるという作業を自動的に行います。出力はプロジェクトのあるフォルダ下に 'Gerber' というサブフォルダを作成してそこに納めます。

FusionPCB と Elecrow は必要とするファイルの拡張子は同じなのですが推奨する条件が多少違っているので、一応切り替えやすいように先頭の方にまとめています。ダイアログを出して設定したりできるようにするべきかも知れませんが、まあそのうち KiCad 安定版で動作するようになったら...

設定説明FusionPCBElecrow
merge_npthドリルファイルのメッキなしとありを1ファイルにまとめるかまとめるのが推奨 (多分別ファイル xxx-NPTH.TXT を添付でもやってくれるとは思う)別ファイル推奨。まとめた時はメッキありになっちゃうかも
use_aux_origin補助座標 AuxOrigin を使うかどうかどちらかと言うと KiCad 側のドリルファイルの補助座標を Python から扱うやり方が正しいかどうか多少曖昧な所があるけど多分どちらでも大丈夫
excellon_formatドリルファイルの数値のフォーマットサプレスリーディング推奨推奨は不明だけど、DECIMAL_FORMAT で発注している人が多そう


動作は Windows 10 + KiCad (6.0.0-rc1-dev-536-g40a3b4a53) 環境でしか確認していません。

# file : action_menu_gerber_zip.py
#
# (gerber_zip)
#
# Copyright (C) 2018 g200kg
#   Released under MIT License
#

import pcbnew
from pcbnew import *
import wx
import os
import zipfile

#
# Setup params
#

gerber_subdir = "Gerber"
merge_npth = False
use_aux_origin = True
excellon_format = EXCELLON_WRITER.DECIMAL_FORMAT
#excellon_format = EXCELLON_WRITER.SUPPRESS_LEADING

#
#
#

class GerberZip( pcbnew.ActionPlugin ):
    '''
    Make Gerber Zip-file for Elecrow / FusionPCB
    '''
    layers = [
        [ F_Cu,     'GTL', None ],
        [ B_Cu,     'GBL', None ],
        [ F_SilkS,  'GTO', None ],
        [ B_SilkS,  'GBO', None ],
        [ F_Mask,   'GTS', None ],
        [ B_Mask,   'GBS', None ],
        [ Edge_Cuts,'GML', None ],
        [ In1_Cu,   'GL2', None ],
        [ In2_Cu,   'GL3', None ],
        [ In3_Cu,   'GL4', None ],
        [ In4_Cu,   'GL5', None ],
    ]
    max_layer = 7

    def defaults( self ):
        self.name = "Make Gerber-Zip (Elecrow/FusionPCB style)"
        self.category = "Plot"
        self.description = "Make Gerber-Zip-file for Elecrow / FusionPCB"

    def forcedel( self, fname ):
        if os.path.exists(fname):
            os.remove(fname)

    def forceren( self, src, dst ):
        self.forcedel(dst)
        os.rename(src, dst)

    def Run( self ):
        board = pcbnew.GetBoard()
        board_fname = board.GetFileName()
        board_dir = os.path.dirname(board_fname)
        board_basename = (os.path.splitext(os.path.basename(board_fname)))[0]
        gerber_dir = '%s/%s' % (board_dir, gerber_subdir)
        drill_fname = '%s/%s.TXT' % (gerber_dir, board_basename)
        npth_fname = '%s/%s-NPTH.TXT' % (gerber_dir, board_basename)
        zip_fname = '%s/%s.zip' % (gerber_dir, board_basename)
        if not os.path.exists(gerber_dir):
            os.mkdir(gerber_dir)

        max_layer = board.GetCopperLayerCount() + 5
# PLOT
        pc = pcbnew.PLOT_CONTROLLER(board)
        po = pc.GetPlotOptions()

        po.SetOutputDirectory(gerber_dir)
        po.SetPlotValue(True)
        po.SetPlotReference(True)
        po.SetExcludeEdgeLayer(False)
        po.SetLineWidth(FromMM(0.1))
        po.SetSubtractMaskFromSilk(True)
        po.SetUseAuxOrigin(use_aux_origin)

        for layer in self.layers:
            targetname = '%s/%s.%s' % (gerber_dir, board_basename, layer[1])
            self.forcedel(targetname)
        self.forcedel(drill_fname)
        self.forcedel(npth_fname)
        self.forcedel(zip_fname)

        for i in range(max_layer):
            layer = self.layers[i]
            pc.SetLayer(layer[0])
            pc.OpenPlotfile(layer[1],PLOT_FORMAT_GERBER,layer[1])
            pc.PlotLayer()
            layer[2] = pc.GetPlotFileName()
        pc.ClosePlot()

        for i in range(max_layer):
            layer = self.layers[i]
            targetname = '%s/%s.%s' % (gerber_dir, board_basename, layer[1])
            self.forceren(layer[2],targetname)
# DRILL
        ew = EXCELLON_WRITER(board)
        ew.SetFormat(True, excellon_format, 3, 3)
        offset = wxPoint(0,0)
        if(use_aux_origin):
            offset = board.GetAuxOrigin()
        ew.SetOptions(False, False, offset, merge_npth)
        ew.CreateDrillandMapFilesSet(gerber_dir,True,False)
        if merge_npth:
            self.forceren('%s/%s.drl' % (gerber_dir, board_basename), drill_fname)
        else:
            self.forceren('%s/%s-PTH.drl' % (gerber_dir, board_basename), drill_fname)
            self.forceren('%s/%s-NPTH.drl' % (gerber_dir, board_basename), npth_fname)
# ZIP
        with zipfile.ZipFile(zip_fname,'w') as f:
            for i in range(max_layer):
                layer = self.layers[i]
                targetname = '%s/%s.%s' % (gerber_dir, board_basename, layer[1])
                f.write(targetname, os.path.basename(targetname))
            f.write(drill_fname, os.path.basename(drill_fname))
            if not merge_npth:
                f.write(npth_fname, os.path.basename(npth_fname))

        wx.MessageBox('GerberZip Complete.\n\n%s' % zip_fname, 'Gerber Zip', wx.OK|wx.ICON_INFORMATION)

GerberZip().register()

Posted by g200kg : 2018/09/30 13:45:06