diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b339f4 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Some KiCad plugins in Python + +Thoses plugins must be copied inside KiCad's python plugins +directory (~/.kicad_plugins or /usr/share/kicad/scripting/plugins/ for +Linux). +Most of them use python plugin (Action plugins) in KiCad. This feature +is enabled in daily builds of KiCad. +See https://forum.kicad.info/t/howto-register-a-python-plugin-inside-pcbnew-tools-menu/5540 + + +## ViaStiching + +A pure python via stiching. + +After select "Via Stiching" in Tools menu, choose your options in the +interface. + +[The interface](images/via1.png) + +Then the result should be: + +[The result](images/via2.png) diff --git a/ViaStiching/FillArea.py b/ViaStiching/FillArea.py new file mode 100755 index 0000000..03ee0a2 --- /dev/null +++ b/ViaStiching/FillArea.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# +# FillArea.py +# +# Copyright 2017 JS Reynaud +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +from __future__ import print_function +from pcbnew import * +import sys +import tempfile +import shutil +import os +import random +import pprint + +"""# This script fill all areas with Via (Via Stitching) is the area is on +# a specific net (by default /GND fallback to GND) +# +# +# Usage in pcbnew's python console: +# First you neet to copy this file (named FillArea.py) in your kicad_plugins +# directory (~/.kicad_plugins/ on Linux) +# Launch pcbnew and open python console (last entry of Tools menu) +# Then enter the following line (one by one, Hit enter after each) +import FillArea +FillArea.FillArea().Run() + + +# Other example: +# You can add modifications to parameters by adding functions call: +FillArea.FillArea().SetDebug().SetNetname("GND").SetStepMM(1.27).SetSizeMM(0.6).SetDrillMM(0.3).SetClearanceMM(0.2).Run() + +# with +# SetDebug: Activate debug mode (print evolution of the board in ascii art) +# SetNetname: Change the netname to consider for the filling +# (default is /GND or fallback to GND) +# SetStepMM: Change step between Via (in mm) +# SetSizeMM: Change Via copper size (in mm) +# SetDrillMM: Change Via drill hole size (in mm) +# SetClearanceMM: Change clearance for Via (in mm) + +# You can also use it in command line. In this case, the first parameter is +# the pcb file path. Default options are applied. + +""" + + +class FillArea: + """ + Automaticaly add via on area where there are no track/existing via, + pads and keepout areas + """ + + def __init__(self, filename=None): + self.filename = None + self.clearance = 0 + # Net name to use + self.SetPCB(GetBoard()) + # Set the filename + self.SetFile(filename) + # Step between via + self.SetStepMM(2.54) + # Size of the via (diameter of copper) + self.SetSizeMM(0.35) + # Size of the drill (diameter) + self.SetDrillMM(0.30) + # Isolation between via and other elements + # ie: radius from the border of the via + self.SetClearanceMM(0.2) + self.only_selected_area = False + if self.pcb is not None: + for lnet in ["GND", "/GND"]: + if self.pcb.FindNet(lnet) is not None: + self.SetNetname(lnet) + break + self.netname = None + self.debug = False + self.random = False + if self.netname is None: + self.SetNetname("GND") + + self.tmp_dir = None + + def SetFile(self, filename): + self.filename = filename + if self.filename: + self.SetPCB(LoadBoard(self.filename)) + + def SetDebug(self): + self.debug = True + return self + + def SetRandom(self): + random.seed() + self.random = True + return self + + def SetPCB(self, pcb): + self.pcb = pcb + if self.pcb is not None: + self.pcb.BuildListOfNets() + return self + + def SetNetname(self, netname): + self.netname = netname + return self + + def SetStepMM(self, s): + self.step = float(FromMM(s)) + return self + + def SetSizeMM(self, s): + self.size = float(FromMM(s)) + return self + + def SetDrillMM(self, s): + self.drill = float(FromMM(s)) + return self + + def OnlyOnSelectedArea(self): + self.only_selected_area = True + return self + + def SetClearanceMM(self, s): + self.clearance = float(FromMM(s)) + return self + + def PrintRect(self, rectangle): + """debuging tool + Print board in ascii art + """ + for y in range(rectangle[0].__len__()): + for x in range(rectangle.__len__()): + print("%X" % rectangle[x][y], end='') + print() + print() + + def PrepareFootprint(self): + """Don't use via since it's not possible to force a Net. + So use a fake footprint (only one THPad) + """ + self.tmp_dir = tempfile.mkdtemp(".pretty") + module_txt = """(module VIA_MATRIX (layer F.Cu) (tedit 5862471A) + (fp_text reference REF** (at 0 0) (layer F.SilkS) hide + (effects (font (size 0 0) (thickness 0.0))) + ) + (fp_text value VIA_MATRIX (at 0 0) (layer F.Fab) hide + (effects (font (size 0 0) (thickness 0.0))) + ) + (pad 1 thru_hole circle (at 0 0) (size 1.5 1.5) (drill 0.762) (layers *.Cu)) +)""" + + # Create the footprint on a temp directory + f = open(os.path.join(self.tmp_dir, "VIA_MATRIX.kicad_mod"), 'w') + f.write(module_txt) + f.close() + + plugin = IO_MGR.PluginFind(IO_MGR.KICAD) + module = plugin.FootprintLoad(self.tmp_dir, "VIA_MATRIX") + module.FindPadByName("1").SetSize(wxSize(self.size, self.size)) + module.FindPadByName("1").SetDrillSize(wxSize(self.drill, self.drill)) + module.FindPadByName("1").SetLocalClearance(int(self.clearance)) + module.FindPadByName("1").SetNet(self.pcb.FindNet(self.netname)) + module.FindPadByName("1").SetZoneConnection(PAD_ZONE_CONN_FULL) + return module + + def CleanupFootprint(self): + """ + cleanup temp footprint + """ + if self.tmp_dir and os.path.isdir(self.tmp_dir): + shutil.rmtree(self.tmp_dir) + + def AddModule(self,module,position,x,y): + m = MODULE(module) + m.SetPosition(position) + m.SetReference("V%s_%s" % (x, y)) + m.SetValue("AUTO_VIA") + m.SetLastEditTime() + m.thisown = 0 + self.pcb.AddNative(m,ADD_APPEND) + m.SetFlag(IS_NEW) + + def Run(self): + """ + Launch the process + """ + lboard = self.pcb.ComputeBoundingBox() + rectangle = [] + origin = lboard.GetPosition() + module = self.PrepareFootprint() + + # Create an initial rectangle: all is off + # get a margin to avoid out of range + # Values: + # 0 => position is ok for via + # != 0 => position is not ok. + # Number is for debuging: check what feature is disabling this position + l_clearance = self.step + self.clearance + self.size + x_limit = int((lboard.GetWidth() + l_clearance) / self.step) + 1 + y_limit = int((lboard.GetHeight() + l_clearance) / self.step) + 1 + for x in range(0, x_limit): + rectangle.append([]) + for y in range(0, y_limit): + rectangle[x].append(0x8) + + if self.debug: + print("Initial rectangle") + self.PrintRect(rectangle) + + # Enum all area + for i in range(self.pcb.GetAreaCount()): + area = self.pcb.GetArea(i) + # If use only selected area mode + # only_selected_area | is selected | keepout | result + # False | X | X | True + # True | True | X | True + # True | False | True | True + # True | False | False | False + if not (self.only_selected_area and not area.IsSelected() and not area.GetIsKeepout()): + # Handle only area on same target net of keepout are + if area.GetNetname() == self.netname or area.GetIsKeepout(): + keepOutMode = area.GetIsKeepout() + for y in range(rectangle[0].__len__()): + for x in range(rectangle.__len__()): + testResult = not keepOutMode # = False if is Keepout + offset = self.clearance + self.size/2 + #offset = int(self.inter / 2) + # For keepout area: Deny Via + # For same net area: Allow if not denied by keepout + current_x = origin.x + (x * self.step) + current_y = origin.y + (y * self.step) + for dx in [-offset, offset]: + for dy in [-offset, offset]: + r = area.HitTestFilledArea(wxPoint(current_x + dx, + current_y + dy)) + if keepOutMode: + testResult |= r + else: + testResult &= r + + if keepOutMode: + testResult |= r + else: + testResult &= r + if testResult: + if keepOutMode: + rectangle[x][y] = 0x1 + else: + # Allow only if it's first step disabling + # ie: keepout are keeped + if rectangle[x][y] == 0x8: + rectangle[x][y] = 0 + + if self.debug: + print("Post Area handling") + self.PrintRect(rectangle) + + # Same job with all pads + for pad in self.pcb.GetPads(): + local_offset = max(pad.GetClearance(),self.clearance) + self.size/2 + max_size = max(pad.GetSize().x,pad.GetSize().y) + start_x = int(floor(((pad.GetPosition().x - (max_size / 2.0 + + local_offset)) - origin.x) / self.step)) + stop_x = int(ceil(((pad.GetPosition().x + (max_size / 2.0 + + local_offset)) - origin.x) / self.step)) + + start_y = int(floor(((pad.GetPosition().y - (max_size / 2.0 + + local_offset)) - origin.y) / self.step)) + stop_y = int(ceil(((pad.GetPosition().y + (max_size / 2.0 + + local_offset)) - origin.y) / self.step)) + + for x in range(start_x, stop_x + 1): + for y in range(start_y, stop_y + 1): + start_rect = wxPoint(origin.x + (self.step * x) - + local_offset, + origin.y + (self.step * y) - + local_offset) + size_rect = wxSize(2 * local_offset, 2 * local_offset) + if pad.HitTest(EDA_RECT(start_rect, size_rect), False): + rectangle[x][y] |= 0x2 + + # Same job with tracks + for track in self.pcb.GetTracks(): + start_x = track.GetStart().x + start_y = track.GetStart().y + + stop_x = track.GetEnd().x + stop_y = track.GetEnd().y + + if start_x > stop_x: + d = stop_x + stop_x = start_x + start_x = d + + if start_y > stop_y: + d = stop_y + stop_y = start_y + start_y = d + + osx = start_x + osy = start_y + opx = stop_x + opy = stop_y + + clearance = max(track.GetClearance(),self.clearance) + self.size/2 + track.GetWidth()/2 + + start_x = int(floor(((start_x - clearance) - + origin.x) / self.step)) + stop_x = int(ceil(((stop_x + clearance) - origin.x) / self.step)) + + start_y = int(floor(((start_y - clearance) - + origin.y) / self.step)) + stop_y = int(ceil(((stop_y + clearance) - origin.y) / self.step)) + + for x in range(start_x, stop_x + 1): + for y in range(start_y, stop_y + 1): + start_rect = wxPoint(origin.x + (self.step * x) - + clearance, + origin.y + (self.step * y) - + clearance) + size_rect = wxSize(2 * clearance, 2 * clearance) + if track.HitTest(EDA_RECT(start_rect, size_rect), False): + rectangle[x][y] |= 0x4 + + if self.debug: + print("Post Pad + tracks") + self.PrintRect(rectangle) + + # Same job with existing text + for draw in self.pcb.m_Drawings: + if (draw.GetClass() == 'PTEXT' and + self.pcb.GetLayerID(draw.GetLayerName()) in (F_Cu, B_Cu)): + inter = float(self.clearance + self.size) + bbox = draw.GetBoundingBox() + start_x = int(floor(((bbox.GetPosition().x - inter) - + origin.x) / self.step)) + stop_x = int(ceil(((bbox.GetPosition().x + + (bbox.GetSize().x + inter)) - + origin.x) / self.step)) + + start_y = int(floor(((bbox.GetPosition().y - inter) - + origin.y) / self.step)) + stop_y = int(ceil(((bbox.GetPosition().y + + (bbox.GetSize().y + inter)) - + origin.y) / self.step)) + + for x in range(start_x, stop_x + 1): + for y in range(start_y, stop_y + 1): + rectangle[x][y] |= 0xA + + if self.debug: + print("Post Drawnings: final result (0: mean ok for a via)") + self.PrintRect(rectangle) + + for y in range(rectangle[0].__len__()): + for x in range(rectangle.__len__()): + if rectangle[x][y] == 0: + ran_x = 0 + ran_y = 0 + if self.random: + ran_x = (random.random() * self.step / 2.0) - (self.step / 4.0) + ran_y = (random.random() * self.step / 2.0) - (self.step / 4.0) + self.AddModule(module,wxPoint(origin.x + (self.step * x) + ran_x, + origin.y + (self.step * y) + ran_y),x,y) + + if self.filename: + self.pcb.Save(self.filename) + self.CleanupFootprint() + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: %s " % sys.argv[0]) + else: + import sys + FillArea(sys.argv[1]).SetDebug().Run() diff --git a/ViaStiching/FillAreaAction.py b/ViaStiching/FillAreaAction.py new file mode 100644 index 0000000..d253e37 --- /dev/null +++ b/ViaStiching/FillAreaAction.py @@ -0,0 +1,62 @@ +# +# FillAreaAction.py +# +# Copyright 2017 JS Reynaud +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +import pcbnew +import FillArea +import wx +import FillAreaDialog + + +class FillAreaAction(pcbnew.ActionPlugin): + def defaults(self): + self.name = "Via stiching WX" + self.category = "Undefined" + self.description = "" + + def Run(self): + a = FillAreaDialog.FillAreaDialog(None) + a.m_SizeMM.SetValue("0.35") + a.m_StepMM.SetValue("2.54") + a.m_DrillMM.SetValue("0.3") + a.m_Netname.SetValue("auto") + a.m_ClearanceMM.SetValue("0.2") + + if a.ShowModal() == wx.ID_OK: + fill = FillArea.FillArea() + try: + fill.SetStepMM(float(a.m_StepMM.GetValue())) + fill.SetSizeMM(float(a.m_SizeMM.GetValue())) + fill.SetDrillMM(float(a.m_DrillMM.GetValue())) + fill.SetClearanceMM(float(a.m_ClearanceMM.GetValue())) + if a.m_Netname.GetValue() != "auto": + fill.SetNetname(a.m_Netname.GetValue()) + if a.m_Debug.IsChecked(): + fill.SetDebug() + if a.m_Random.IsChecked(): + fill.SetRandom() + if a.m_only_selected.IsChecked(): + fill.OnlyOnSelectedArea() + fill.Run() + except Exception: + wx.MessageDialog(None,"Invalid parameter") + else: + print "Cancel" + a.Destroy() + +FillAreaAction().register() diff --git a/ViaStiching/FillAreaDialog.py b/ViaStiching/FillAreaDialog.py new file mode 100644 index 0000000..77d1e63 --- /dev/null +++ b/ViaStiching/FillAreaDialog.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +########################################################################### +## Python code generated with wxFormBuilder (version Feb 16 2016) +## http://www.wxformbuilder.org/ +## +## PLEASE DO "NOT" EDIT THIS FILE! +########################################################################### + +import wx +import wx.xrc + +########################################################################### +## Class FillAreaDialog +########################################################################### + +class FillAreaDialog ( wx.Dialog ): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"Fill Area parameters", pos = wx.DefaultPosition, size = wx.Size( 369,356 ), style = wx.DEFAULT_DIALOG_STYLE ) + + self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize ) + + fgSizer1 = wx.FlexGridSizer( 0, 2, 0, 0 ) + fgSizer1.SetFlexibleDirection( wx.BOTH ) + fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, u"Via copper size", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText3.Wrap( -1 ) + fgSizer1.Add( self.m_staticText3, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_SizeMM = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_SizeMM.SetMinSize( wx.Size( 1000,-1 ) ) + + fgSizer1.Add( self.m_SizeMM, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText9 = wx.StaticText( self, wx.ID_ANY, u"Via drill size", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText9.Wrap( -1 ) + fgSizer1.Add( self.m_staticText9, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_DrillMM = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_DrillMM, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText5 = wx.StaticText( self, wx.ID_ANY, u"Via clearance", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText5.Wrap( -1 ) + fgSizer1.Add( self.m_staticText5, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_ClearanceMM = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_ClearanceMM, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText6 = wx.StaticText( self, wx.ID_ANY, u"Net name", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText6.Wrap( -1 ) + fgSizer1.Add( self.m_staticText6, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_Netname = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_Netname, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, u"Step between via", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText2.Wrap( -1 ) + fgSizer1.Add( self.m_staticText2, 0, wx.ALL, 5 ) + + self.m_StepMM = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_StepMM, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText7 = wx.StaticText( self, wx.ID_ANY, u"Debug mode", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText7.Wrap( -1 ) + fgSizer1.Add( self.m_staticText7, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_Debug = wx.CheckBox( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_Debug, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText8 = wx.StaticText( self, wx.ID_ANY, u"Random it", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText8.Wrap( -1 ) + fgSizer1.Add( self.m_staticText8, 0, wx.ALL, 5 ) + + self.m_Random = wx.CheckBox( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_Random, 0, wx.ALL, 5 ) + + self.m_staticText81 = wx.StaticText( self, wx.ID_ANY, u"Only under selected Zone", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText81.Wrap( -1 ) + fgSizer1.Add( self.m_staticText81, 0, wx.ALL, 5 ) + + self.m_only_selected = wx.CheckBox( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + fgSizer1.Add( self.m_only_selected, 0, wx.ALL, 5 ) + + self.m_staticText71 = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText71.Wrap( -1 ) + fgSizer1.Add( self.m_staticText71, 0, wx.ALL, 5 ) + + bSizer1 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_button1 = wx.Button( self, wx.ID_OK, u"Run", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_button1.SetDefault() + bSizer1.Add( self.m_button1, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) + + self.m_button2 = wx.Button( self, wx.ID_CANCEL, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer1.Add( self.m_button2, 0, wx.ALL, 5 ) + + + fgSizer1.Add( bSizer1, 1, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 5 ) + + + self.SetSizer( fgSizer1 ) + self.Layout() + + self.Centre( wx.BOTH ) + + def __del__( self ): + pass + + diff --git a/ViaStiching/FillAreaTpl.fbp b/ViaStiching/FillAreaTpl.fbp new file mode 100644 index 0000000..819391d --- /dev/null +++ b/ViaStiching/FillAreaTpl.fbp @@ -0,0 +1,1759 @@ + + + + + + Python + 1 + source_name + 0 + 0 + res + UTF-8 + connect + KiCad/FillAreaDialog + 1000 + none + 0 + FillAreaDialog + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + FillAreaDialog + + 369,356 + wxDEFAULT_DIALOG_STYLE + + Fill Area parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + wxBOTH + + + 0 + + fgSizer1 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Via copper size + + 0 + + + 0 + + 1 + m_staticText3 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + 1000,-1 + 1 + m_SizeMM + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxTextValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Via drill size + + 0 + + + 0 + + 1 + m_staticText9 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_DrillMM + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Via clearance + + 0 + + + 0 + + 1 + m_staticText5 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_ClearanceMM + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Net name + + 0 + + + 0 + + 1 + m_staticText6 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_Netname + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Step between via + + 0 + + + 0 + + 1 + m_staticText2 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + m_StepMM + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Debug mode + + 0 + + + 0 + + 1 + m_staticText7 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_Debug + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Random it + + 0 + + + 0 + + 1 + m_staticText8 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_Random + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Only under selected Zone + + 0 + + + 0 + + 1 + m_staticText81 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_only_selected + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_staticText71 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL + 1 + + + bSizer1 + wxHORIZONTAL + none + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + + 1 + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_OK + Run + + 0 + + + 0 + + 1 + m_button1 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_CANCEL + Cancel + + 0 + + + 0 + + 1 + m_button2 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/via1.png b/images/via1.png new file mode 100644 index 0000000..4b6ac11 Binary files /dev/null and b/images/via1.png differ diff --git a/images/via2.png b/images/via2.png new file mode 100644 index 0000000..9b16c79 Binary files /dev/null and b/images/via2.png differ