├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── __init__.py ├── __main__.py ├── dialog.py ├── doc ├── fanout_tool.png └── pcm.png ├── fanout.fbp ├── icon ├── icon_256x256.png ├── icon_512x512.png └── icon_64x64.png ├── metadata.json.template ├── onekiwi ├── controller │ ├── controller.py │ ├── logtext.py │ ├── package.json │ ├── package.py │ └── package.yaml ├── icon.png ├── image │ ├── bothsides.png │ ├── bothsides.svg │ ├── bottomleft.png │ ├── bottomleft.svg │ ├── bottomright.png │ ├── bottomright.svg │ ├── coming.png │ ├── counterclock.png │ ├── counterclock.svg │ ├── counterclockwise.png │ ├── counterclockwise.svg │ ├── horizontal.png │ ├── horizontal.svg │ ├── image.svg │ ├── inside.png │ ├── inside.svg │ ├── outside.png │ ├── outside.svg │ ├── quadrant.png │ ├── quadrant.svg │ ├── topleft.png │ ├── topleft.svg │ ├── topright.png │ ├── topright.svg │ ├── vertical.png │ └── vertical.svg ├── kicad │ └── board.py ├── model │ ├── bga.py │ └── model.py ├── plugin.py ├── version.py └── view │ ├── dialog.py │ └── view.py └── release.sh /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | *.log 4 | fanout-tool-*.zip 5 | metadata.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OneKiwi Technology Co., Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @echo "Run test" 3 | python3 dialog.py 4 | 5 | release: release.sh 6 | @echo "Create release" 7 | ./release.sh 8 | 9 | install: 10 | @echo "Install Plugin" 11 | mkdir fanout-tool 12 | cp __init__.py fanout-tool/ 13 | cp -r onekiwi/ fanout-tool/ 14 | rm -rf ~/.local/share/kicad/7.0/scripting/plugins/fanout-tool/ 15 | mv fanout-tool/ ~/.local/share/kicad/7.0/scripting/plugins 16 | 17 | uninstall: 18 | @echo "Uninstall Plugin" 19 | rm -rf ~/.local/share/kicad/7.0/scripting/plugins/fanout-tool/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![icon](onekiwi/icon.png) Fanout Tool 2 | 3 | [![Donate](https://img.shields.io/badge/PayPal-Buy%20Me%20a%20Coffee-brightgreen?style=flat&logo=PayPal)](https://paypal.me/phutruong811) 4 | 5 | ## 6 | 7 | 8 | 9 | ## GUI 10 | ![screenshot](doc/fanout_tool.png) 11 | 12 | ## Installation 💾 13 | 14 | Add our custom repo to **the Plugin and Content Manager**, the URL is `https://raw.githubusercontent.com/OneKiwiTech/onekiwi-kicad-repository/main/repository.json` 15 | 16 | ![pcm](doc/pcm.png) 17 | 18 | From there you can install the plugin via the GUI. 19 | 20 | 21 | ## Demo Video 22 | [![Watch the video](https://img.youtube.com/vi/-J81S3inhoc/sddefault.jpg)](https://youtu.be/-J81S3inhoc) 23 | 24 | ## Licence and credits 25 | Plugin code is licensed under MIT license, see LICENSE for more info. 26 | KiCad Plugin code/structure from: 27 | - [kicad-jlcpcb-tools](https://github.com/Bouni/kicad-jlcpcb-tools) 28 | - [wiki.wxpython.org](https://wiki.wxpython.org/ModelViewController) 29 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .onekiwi.plugin import FanoutAction # Note the relative import! 2 | FanoutAction().register() # Instantiate and register to Pcbnew -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/__main__.py -------------------------------------------------------------------------------- /dialog.py: -------------------------------------------------------------------------------- 1 | try: 2 | import pcbnew 3 | except: 4 | import sys 5 | sys.path.insert(0,"/usr/lib/python3.8/site-packages/") 6 | import pcbnew 7 | import wx 8 | 9 | from onekiwi.controller.controller import Controller 10 | 11 | filename = '/home/vanson/working/kicad/onekiwi/som-imx8qxp-fbga609/som-imx8qxp-fbga609.kicad_pcb' 12 | 13 | class SimplePluginApp(wx.App): 14 | def OnInit(self): 15 | try: 16 | board = pcbnew.LoadBoard(filename) 17 | controller = Controller(board) 18 | controller.Show() 19 | return True 20 | except OSError: 21 | print("OSError: Unable to open file for reading.") 22 | return 0 23 | 24 | def main(): 25 | app = SimplePluginApp() 26 | app.MainLoop() 27 | 28 | print("Done") 29 | 30 | if __name__ == "__main__": 31 | main() -------------------------------------------------------------------------------- /doc/fanout_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/doc/fanout_tool.png -------------------------------------------------------------------------------- /doc/pcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/doc/pcm.png -------------------------------------------------------------------------------- /icon/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/icon/icon_256x256.png -------------------------------------------------------------------------------- /icon/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/icon/icon_512x512.png -------------------------------------------------------------------------------- /icon/icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/icon/icon_64x64.png -------------------------------------------------------------------------------- /metadata.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://go.kicad.org/pcm/schemas/v1", 3 | "name": "Fanout Tool", 4 | "description": "BGA Fanout", 5 | "description_full": "BGA fanout routing", 6 | "identifier": "vn.onekiwi.fanout-tool", 7 | "type": "plugin", 8 | "author": { 9 | "name": "OneKiwi", 10 | "contact": { 11 | "web": "https://github.com/OneKiwiTech" 12 | } 13 | }, 14 | "license": "MIT", 15 | "resources": { 16 | "homepage": "https://github.com/OneKiwiTech/kicad-fanout-tool", 17 | "demo": "https://www.youtube.com/watch?v=-J81S3inhoc" 18 | }, 19 | "versions": [ 20 | { 21 | "version": "VERSION", 22 | "status": "stable", 23 | "kicad_version": "7.0", 24 | "download_sha256": "SHA256", 25 | "download_size": DOWNLOAD_SIZE, 26 | "download_url": "https://github.com/OneKiwiTech/kicad-fanout-tool/releases/download/VERSION/fanout-tool-VERSION.zip", 27 | "install_size": INSTALL_SIZE, 28 | "platforms": [ 29 | "linux", 30 | "macos", 31 | "windows" 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /onekiwi/controller/controller.py: -------------------------------------------------------------------------------- 1 | from ..model.model import Model 2 | from ..view.view import FanoutView 3 | from .logtext import LogText 4 | import sys 5 | import logging 6 | import logging.config 7 | import wx 8 | import pcbnew 9 | from .package import get_packages 10 | 11 | class Controller: 12 | def __init__(self, board): 13 | self.view = FanoutView() 14 | self.board = board 15 | self.reference = None 16 | self.skip = None 17 | self.tracks = [] 18 | self.vias = [] 19 | self.packages = get_packages() 20 | self.logger = self.init_logger(self.view.textLog) 21 | self.model = Model(self.board, self.logger) 22 | 23 | # Connect Events 24 | self.view.buttonFanout.Bind(wx.EVT_BUTTON, self.OnButtonFanout) 25 | self.view.buttonUndo.Bind(wx.EVT_BUTTON, self.OnButtonUndo) 26 | self.view.buttonClear.Bind(wx.EVT_BUTTON, self.OnButtonClear) 27 | self.view.buttonClose.Bind(wx.EVT_BUTTON, self.OnButtonClose) 28 | self.view.choicePackage.Bind( wx.EVT_CHOICE, self.OnChoicePackage) 29 | self.view.choiceAlignment.Bind( wx.EVT_CHOICE, self.OnChoiceAlignment) 30 | self.view.choiceDirection.Bind( wx.EVT_CHOICE, self.OnChoiceDirection) 31 | self.view.editFiltter.Bind(wx.EVT_TEXT, self.OnFiltterChange) 32 | self.view.Bind(wx.EVT_CLOSE, self.OnClose) #解决进程驻留问题 33 | 34 | self.add_references() 35 | self.get_tracks_vias() 36 | self.set_package() 37 | 38 | def Show(self): 39 | self.view.Show() 40 | 41 | def Close(self): 42 | self.view.Destroy() 43 | def OnClose(self,event): #解决进程驻留问题 44 | self.view.Destroy() 45 | event.Skip() 46 | def OnButtonFanout(self, event): 47 | reference = self.view.GetReferenceSelected() 48 | if reference == '': 49 | self.logger.error('Please chose a Reference') 50 | return 51 | else: 52 | self.logger.info('Selected reference: %s' %reference) 53 | 54 | if len(self.tracks) > 0: 55 | track_index = self.view.GetTrackSelectedIndex() 56 | else: 57 | self.logger.error('Please add track width') 58 | return 59 | if len(self.tracks) > 0: 60 | via_index = self.view.GetViaSelectedIndex() 61 | else: 62 | self.logger.error('Please add via') 63 | return 64 | skip_index = self.view.GetSkipIndex() 65 | unused_pads = self.view.GetCheckUnusepad() 66 | package = self.view.GetPackageValue() 67 | self.logger.info('package: %s' %package) 68 | alignment = self.view.GetAlignmentValue() 69 | self.logger.info('alignment: %s' %alignment) 70 | if package == 'BGA' and alignment == 'Quadrant': 71 | direction = 'none' 72 | else: 73 | direction = self.view.GetDirectionValue() 74 | self.logger.info('direction: %s' %direction) 75 | self.model.update_data(reference,skip_index, self.tracks[track_index], self.vias[via_index],unused_pads) 76 | self.model.update_package(package, alignment, direction) 77 | self.model.fanout() 78 | 79 | def OnButtonUndo(self, event): 80 | self.model.remove_track_via() 81 | 82 | def OnButtonClear(self, event): 83 | self.view.textLog.SetValue('') 84 | 85 | def OnButtonClose(self, event): 86 | self.Close() 87 | 88 | def OnChoicePackage(self, event): 89 | index = event.GetEventObject().GetSelection() 90 | value = event.GetEventObject().GetString(index) 91 | package = self.packages[index] 92 | alignments = [] 93 | directions = [] 94 | for i, ali in enumerate(package.alignments, 0): 95 | alignments.append(ali.name) 96 | if i == 0: 97 | for direc in ali.directions: 98 | directions.append(direc.name) 99 | self.view.ClearAlignment() 100 | self.view.ClearDirection() 101 | if value == 'BGA staggered': 102 | alignments.clear() 103 | self.view.AddAlignment('none') 104 | if value == 'BGA': 105 | directions.clear() 106 | self.view.AddAlignment(alignments) 107 | self.view.AddDirection(directions) 108 | image = self.packages[index].alignments[0].directions[0].image 109 | self.view.SetImagePreview(image) 110 | 111 | def OnChoiceAlignment(self, event): 112 | x = self.view.GetPackageIndex() 113 | y = self.view.GetAlignmentIndex() 114 | value = self.view.GetAlignmentValue() 115 | directions = [] 116 | direcs = self.packages[x].alignments[y].directions 117 | for direc in direcs: 118 | directions.append(direc.name) 119 | image = direcs[0].image 120 | self.view.ClearDirection() 121 | if value == 'Quadrant': 122 | directions.clear() 123 | self.view.AddDirection(directions) 124 | self.view.SetImagePreview(image) 125 | 126 | def OnChoiceDirection(self, event): 127 | x = self.view.GetPackageIndex() 128 | y = self.view.GetAlignmentIndex() 129 | i = event.GetEventObject().GetSelection() 130 | #value = event.GetEventObject().GetString(i) 131 | image = self.packages[x].alignments[y].directions[i].image 132 | self.view.SetImagePreview(image) 133 | 134 | def OnFiltterChange(self, event): 135 | self.logger.info('OnFiltterChange') 136 | value = event.GetEventObject().GetValue() 137 | self.logger.info('text: %s' %value) 138 | self.view.ClearReferences() 139 | for ref in self.model.references: 140 | if ref.rfind(value) != -1: 141 | self.view.AddReferences(ref) 142 | self.view.SetIndexReferences(0) 143 | 144 | def add_references(self): 145 | self.view.AddReferences(self.model.references) 146 | if self.model.indexSelected is not None: 147 | self.view.SetIndexReferences(self.model.indexSelected) 148 | 149 | def get_tracks_vias(self): 150 | units = pcbnew.GetUserUnits() 151 | unit = '' 152 | scale = 1 153 | # pcbnew.EDA_UNITS_INCHES = 0 154 | if units == pcbnew.EDA_UNITS_INCH: 155 | unit = 'in' 156 | scale = 25400000 157 | # pcbnew.EDA_UNITS_MILLIMETRES = 1 158 | elif units == pcbnew.EDA_UNITS_MM: 159 | unit = 'mm' 160 | scale = 1000000 161 | # pcbnew.EDA_UNITS_MILS = 5 162 | elif units == pcbnew.EDA_UNITS_MILS: 163 | unit = 'mil' 164 | scale = 25400 165 | else: 166 | unit = 'mil' 167 | scale = 25400 168 | tracks = self.board.GetDesignSettings().m_TrackWidthList 169 | vias = self.board.GetDesignSettings().m_ViasDimensionsList 170 | tracklist = [] 171 | vialist = [] 172 | for track in tracks: 173 | if track > 0: 174 | self.tracks.append(track) 175 | display = str(track/scale) + ' ' + unit 176 | tracklist.append(display) 177 | # pcbnew.VIA_DIMENSION 178 | for via in vias: 179 | if via.m_Diameter > 0: 180 | self.vias.append(via) 181 | diam = via.m_Diameter 182 | hole = via.m_Drill 183 | display = str(diam/scale) + ' / ' + str(hole/scale) + ' ' + unit 184 | vialist.append(display) 185 | self.view.AddTracksWidth(tracklist) 186 | self.view.AddViasSize(vialist) 187 | self.logger.info('get_design_settings') 188 | 189 | def set_package(self): 190 | default = 2 #bga 191 | packages = [] 192 | alignments = [] 193 | for package in self.packages: 194 | packages.append(package.name) 195 | if package.name == 'BGA': 196 | for alig in package.alignments: 197 | alignments.append(alig.name) 198 | self.view.AddPackageType(packages, default) 199 | self.view.AddAlignment(alignments) 200 | image = self.packages[default].alignments[0].directions[0].image 201 | self.view.SetImagePreview(image) 202 | 203 | def init_logger(self, texlog): 204 | root = logging.getLogger() 205 | root.setLevel(logging.DEBUG) 206 | # Log to stderr 207 | handler1 = logging.StreamHandler(sys.stderr) 208 | handler1.setLevel(logging.DEBUG) 209 | # and to our GUI 210 | handler2 = LogText(texlog) 211 | handler2.setLevel(logging.DEBUG) 212 | formatter = logging.Formatter( 213 | "%(asctime)s - %(levelname)s - %(funcName)s - %(message)s", 214 | datefmt="%Y.%m.%d %H:%M:%S", 215 | ) 216 | handler1.setFormatter(formatter) 217 | handler2.setFormatter(formatter) 218 | root.addHandler(handler1) 219 | root.addHandler(handler2) 220 | return logging.getLogger(__name__) 221 | -------------------------------------------------------------------------------- /onekiwi/controller/logtext.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class LogText(logging.StreamHandler): 4 | def __init__(self, textctrl): 5 | logging.StreamHandler.__init__(self) 6 | self.textctrl = textctrl 7 | 8 | def emit(self, record): 9 | try: 10 | msg = self.format(record) 11 | self.textctrl.WriteText(msg + "\n") 12 | self.flush() 13 | except: 14 | pass -------------------------------------------------------------------------------- /onekiwi/controller/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": [ 3 | { 4 | "name": "SOIC", 5 | "alignment": [ 6 | { 7 | "name": "Aligned, multiple rows", 8 | "direction": [ 9 | { 10 | "name": "Inside", 11 | "image": "coming.png" 12 | }, 13 | { 14 | "name": "Outside", 15 | "image": "coming.png" 16 | }, 17 | { 18 | "name": "Both sides", 19 | "image": "coming.png" 20 | } 21 | ] 22 | }, 23 | { 24 | "name": "Alternate, multiple rows", 25 | "direction": [ 26 | { 27 | "name": "Inside", 28 | "image": "coming.png" 29 | }, 30 | { 31 | "name": "Outside", 32 | "image": "coming.png" 33 | }, 34 | { 35 | "name": "Both sides", 36 | "image": "coming.png" 37 | } 38 | ] 39 | }, 40 | { 41 | "name": "Aligned, single row", 42 | "direction": [ 43 | { 44 | "name": "Inside", 45 | "image": "coming.png" 46 | }, 47 | { 48 | "name": "Outside", 49 | "image": "coming.png" 50 | }, 51 | { 52 | "name": "Both sides", 53 | "image": "coming.png" 54 | } 55 | ] 56 | }, 57 | { 58 | "name": "Alternate, single row", 59 | "direction": [ 60 | { 61 | "name": "Inside", 62 | "image": "coming.png" 63 | }, 64 | { 65 | "name": "Outside", 66 | "image": "coming.png" 67 | }, 68 | { 69 | "name": "Both sides", 70 | "image": "coming.png" 71 | } 72 | ] 73 | } 74 | ] 75 | }, 76 | { 77 | "name": "QUAD", 78 | "alignment": [ 79 | { 80 | "name": "Aligned, multiple rows", 81 | "direction": [ 82 | { 83 | "name": "Inside", 84 | "image": "coming.png" 85 | }, 86 | { 87 | "name": "Outside", 88 | "image": "coming.png" 89 | }, 90 | { 91 | "name": "Both sides", 92 | "image": "coming.png" 93 | } 94 | ] 95 | }, 96 | { 97 | "name": "Alternate, multiple rows", 98 | "direction": [ 99 | { 100 | "name": "Inside", 101 | "image": "coming.png" 102 | }, 103 | { 104 | "name": "Outside", 105 | "image": "coming.png" 106 | }, 107 | { 108 | "name": "Both sides", 109 | "image": "coming.png" 110 | } 111 | ] 112 | }, 113 | { 114 | "name": "Aligned, single row", 115 | "direction": [ 116 | { 117 | "name": "Inside", 118 | "image": "coming.png" 119 | }, 120 | { 121 | "name": "Outside", 122 | "image": "coming.png" 123 | }, 124 | { 125 | "name": "Both sides", 126 | "image": "coming.png" 127 | } 128 | ] 129 | }, 130 | { 131 | "name": "Alternate, single row", 132 | "direction": [ 133 | { 134 | "name": "Inside", 135 | "image": "coming.png" 136 | }, 137 | { 138 | "name": "Outside", 139 | "image": "coming.png" 140 | }, 141 | { 142 | "name": "Both sides", 143 | "image": "coming.png" 144 | } 145 | ] 146 | } 147 | ] 148 | }, 149 | { 150 | "name": "BGA", 151 | "alignment": [ 152 | { 153 | "name": "Quadrant", 154 | "direction": [ 155 | { 156 | "name": "noname", 157 | "image": "quadrant.png" 158 | } 159 | ] 160 | }, 161 | { 162 | "name": "Diagonal", 163 | "direction": [ 164 | { 165 | "name": "TopLeft", 166 | "image": "topleft.png" 167 | }, 168 | { 169 | "name": "TopRight", 170 | "image": "topright.png" 171 | }, 172 | { 173 | "name": "BottomLeft", 174 | "image": "bottomleft.png" 175 | }, 176 | { 177 | "name": "BottomRight", 178 | "image": "bottomright.png" 179 | } 180 | ] 181 | }, 182 | { 183 | "name": "X-pattern", 184 | "direction": [ 185 | { 186 | "name": "Counterclock", 187 | "image": "counterclock.png" 188 | }, 189 | { 190 | "name": "Counterclockwise", 191 | "image": "counterclockwise.png" 192 | } 193 | ] 194 | } 195 | ] 196 | }, 197 | { 198 | "name": "BGA staggered", 199 | "alignment": [ 200 | { 201 | "name": "noname", 202 | "direction": [ 203 | { 204 | "name": "Horizontal", 205 | "image": "coming.png" 206 | }, 207 | { 208 | "name": "Vertical", 209 | "image": "coming.png" 210 | } 211 | ] 212 | } 213 | ] 214 | } 215 | ] 216 | } -------------------------------------------------------------------------------- /onekiwi/controller/package.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import List 4 | 5 | class Direction: 6 | def __init__(self, name, image): 7 | self.name = name 8 | self.image = image 9 | 10 | class Alignment: 11 | def __init__(self, name, directions): 12 | self.name = name 13 | self.directions:List[Direction] = directions 14 | 15 | class Package: 16 | def __init__(self, name): 17 | self.name = name 18 | self.alignments:List[Alignment] = [] 19 | 20 | class Packages: 21 | def __init__(self): 22 | self.data = None 23 | self.packages:List[Package] = [] 24 | self.read_yaml() 25 | self.parser_data() 26 | 27 | def read_yaml(self): 28 | yaml_path = os.path.join(os.path.dirname(__file__), 'package.json') # Optional 29 | with open(yaml_path) as f: 30 | #self.data = yaml.load(f, Loader=yaml.FullLoader) 31 | self.data = json.load(f) 32 | 33 | def parser_data(self): 34 | for package in self.data['package']: 35 | name = package['name'] 36 | pack = Package(name) 37 | for alignment in package['alignment']: 38 | ali = alignment['name'] 39 | directions = [] 40 | for direction in alignment['direction']: 41 | direc = Direction(direction['name'], direction['image']) 42 | directions.append(direc) 43 | align = Alignment(ali, directions) 44 | pack.alignments.append(align) 45 | self.packages.append(pack) 46 | 47 | def get_packages(): 48 | packages = Packages() 49 | return packages.packages -------------------------------------------------------------------------------- /onekiwi/controller/package.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | - name: SOIC 3 | alignment: 4 | - name: Aligned, multiple rows 5 | direction: 6 | - name: Inside 7 | image: inside.svg 8 | - name: Outside 9 | image: outside.svg 10 | - name: Both sides 11 | image: bothsides.svg 12 | - name: Alternate, multiple rows 13 | direction: 14 | - name: Inside 15 | image: inside.svg 16 | - name: Outside 17 | image: outside.svg 18 | - name: Both sides 19 | image: bothsides.svg 20 | - name: Aligned, single row 21 | direction: 22 | - name: Inside 23 | image: inside.svg 24 | - name: Outside 25 | image: outside.svg 26 | - name: Both sides 27 | image: bothsides.svg 28 | - name: Alternate, single row 29 | direction: 30 | - name: Inside 31 | image: inside.svg 32 | - name: Outside 33 | image: outside.svg 34 | - name: Both sides 35 | image: bothsides.svg 36 | - name: QUAD 37 | alignment: 38 | - name: Aligned, multiple rows 39 | direction: 40 | - name: Inside 41 | image: inside.svg 42 | - name: Outside 43 | image: outside.svg 44 | - name: Both sides 45 | image: bothsides.svg 46 | - name: Alternate, multiple rows 47 | direction: 48 | - name: Inside 49 | image: inside.svg 50 | - name: Outside 51 | image: outside.svg 52 | - name: Both sides 53 | image: bothsides.svg 54 | - name: Aligned, single row 55 | direction: 56 | - name: Inside 57 | image: inside.svg 58 | - name: Outside 59 | image: outside.svg 60 | - name: Both sides 61 | image: bothsides.svg 62 | - name: Alternate, single row 63 | direction: 64 | - name: Inside 65 | image: inside.svg 66 | - name: Outside 67 | image: outside.svg 68 | - name: Both sides 69 | image: bothsides.svg 70 | - name: BGA 71 | alignment: 72 | - name: Quadrant 73 | direction: 74 | - name: noname 75 | image: quadrant.svg 76 | - name: Diagonal 77 | direction: 78 | - name: TopLeft 79 | image: topleft.svg 80 | - name: TopRight 81 | image: topright.svg 82 | - name: BottomLeft 83 | image: bottomleft.svg 84 | - name: BottomRight 85 | image: bottomright.svg 86 | - name: X-pattern 87 | direction: 88 | - name: Counterclock 89 | image: counterclock.svg 90 | - name: Counterclockwise 91 | image: counterclockwise.svg 92 | - name: BGA staggered 93 | alignment: 94 | - name: noname 95 | direction: 96 | - name: Horizontal 97 | image: horizontal.svg 98 | - name: Vertical 99 | image: vertical.svg -------------------------------------------------------------------------------- /onekiwi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/icon.png -------------------------------------------------------------------------------- /onekiwi/image/bothsides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/bothsides.png -------------------------------------------------------------------------------- /onekiwi/image/bottomleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/bottomleft.png -------------------------------------------------------------------------------- /onekiwi/image/bottomleft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/bottomright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/bottomright.png -------------------------------------------------------------------------------- /onekiwi/image/bottomright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/coming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/coming.png -------------------------------------------------------------------------------- /onekiwi/image/counterclock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/counterclock.png -------------------------------------------------------------------------------- /onekiwi/image/counterclock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/counterclockwise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/counterclockwise.png -------------------------------------------------------------------------------- /onekiwi/image/counterclockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/horizontal.png -------------------------------------------------------------------------------- /onekiwi/image/horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /onekiwi/image/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /onekiwi/image/inside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/inside.png -------------------------------------------------------------------------------- /onekiwi/image/inside.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /onekiwi/image/outside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/outside.png -------------------------------------------------------------------------------- /onekiwi/image/quadrant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/quadrant.png -------------------------------------------------------------------------------- /onekiwi/image/quadrant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/topleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/topleft.png -------------------------------------------------------------------------------- /onekiwi/image/topleft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /onekiwi/image/topright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/topright.png -------------------------------------------------------------------------------- /onekiwi/image/topright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /onekiwi/image/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneKiwiTech/kicad-fanout-tool/db8a28b7b2cfa6ee3c9092ee1eed9aa5a5783db2/onekiwi/image/vertical.png -------------------------------------------------------------------------------- /onekiwi/image/vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /onekiwi/kicad/board.py: -------------------------------------------------------------------------------- 1 | import pcbnew 2 | import os 3 | import re 4 | import wx 5 | 6 | PLUGIN_PATH = os.path.split(os.path.abspath(__file__))[0] 7 | 8 | def get_wxWidgets_version(): 9 | v = re.search(r"wxWidgets\s([\d\.]+)", wx.version()) 10 | v = int(v.group(1).replace(".", "")) 11 | return v 12 | 13 | def get_plugin_version(): 14 | """READ Version from file""" 15 | if not os.path.isfile(os.path.join(PLUGIN_PATH, "VERSION")): 16 | return "unknown" 17 | with open(os.path.join(PLUGIN_PATH, "VERSION")) as f: 18 | return f.read() 19 | 20 | def get_kicad_build_version(): 21 | return str(pcbnew.GetBuildVersion()) 22 | 23 | def get_kicad_semantic_version(): 24 | return str(pcbnew.GetSemanticVersion()) 25 | 26 | def get_kicad_major_minor_version(): 27 | return str(pcbnew.GetMajorMinorVersion()) 28 | 29 | def get_kicad_version(): 30 | version = str(pcbnew.Version()) 31 | major = version.split(".")[0] 32 | minor = version.split(".")[1] 33 | patch = version.split(".")[2] 34 | 35 | def get_current_unit(): 36 | unit = pcbnew.GetUserUnits() 37 | # pcbnew.EDA_UNITS_INCHES = 0 38 | if unit == pcbnew.EDA_UNITS_INCH: 39 | return 'in' 40 | # pcbnew.EDA_UNITS_MILLIMETRES = 1 41 | elif unit == pcbnew.EDA_UNITS_MM: 42 | return 'mm' 43 | # pcbnew.EDA_UNITS_MILS = 5 44 | elif unit == pcbnew.EDA_UNITS_MILS: 45 | return 'mil' 46 | 47 | def get_onekiwi_path(): 48 | # controller dir 49 | current_path = os.path.dirname(__file__) 50 | onekiwi_path = os.path.dirname(current_path) 51 | return onekiwi_path 52 | 53 | def get_image_path(): 54 | onekiwi_path = get_onekiwi_path() 55 | image_path = os.path.join(onekiwi_path, 'image') 56 | return image_path 57 | -------------------------------------------------------------------------------- /onekiwi/model/bga.py: -------------------------------------------------------------------------------- 1 | import pcbnew 2 | import math 3 | 4 | class BGA: 5 | def __init__(self, board, reference,skip, track, via,unused_pads, alignment, direction, logger): 6 | self.logger = logger 7 | self.board = board 8 | self.reference = reference 9 | self.skip=skip 10 | self.track = track 11 | self.via = via 12 | self.unused_pads = unused_pads 13 | self.alignment = alignment 14 | self.direction = direction 15 | self.pitchx = 0 16 | self.pitchy = 0 17 | self.tracks = [] 18 | 19 | self.group = pcbnew.PCB_GROUP(None) 20 | 21 | self.logger.info(reference) 22 | self.radian_pad = 0.0 23 | self.footprint = self.board.FindFootprintByReference(reference) 24 | self.radian = self.footprint.GetOrientation() 25 | self.degrees = self.footprint.GetOrientationDegrees() 26 | self.pads = self.footprint.Pads() 27 | self.x0 = self.footprint.GetPosition().x 28 | self.y0 = self.footprint.GetPosition().y 29 | self.init_data() 30 | 31 | def get_major_version(self): 32 | version = str(pcbnew.Version()) 33 | major = int(version.split(".")[0]) 34 | return major 35 | 36 | def init_data(self): 37 | if self.degrees not in [0.0 , 90.0, 180.0, -90.0]: 38 | degrees = self.degrees + 45.0 39 | self.footprint.SetOrientationDegrees(degrees) 40 | self.radian_pad = self.footprint.GetOrientation() 41 | self.footprint.SetOrientationDegrees(0) 42 | pos_x = [] 43 | pos_y = [] 44 | 45 | minx = self.pads[0].GetPosition().x 46 | maxx = self.pads[0].GetPosition().x 47 | miny = self.pads[0].GetPosition().y 48 | maxy = self.pads[0].GetPosition().y 49 | 50 | pos_x.append([self.pads[0].GetPosition()]) 51 | pos_y.append([self.pads[0].GetPosition()]) 52 | 53 | for pad in self.pads: 54 | pos = pad.GetPosition() 55 | if minx > pos.x: 56 | minx = pos.x 57 | if maxx < pos.x: 58 | maxx = pos.x 59 | if miny > pos.y: 60 | miny = pos.y 61 | if maxy < pos.y: 62 | maxy = pos.y 63 | checkx = True 64 | for arr in pos_x: 65 | if arr[0].y == pos.y and pos not in arr: 66 | checkx = False 67 | arr.append(pos) 68 | if checkx == True: 69 | pos_x.append([pos]) 70 | 71 | checky = True 72 | for arr in pos_y: 73 | if arr[0].x == pos.x and pos not in arr: 74 | checky = False 75 | arr.append(pos) 76 | if checky == True: 77 | pos_y.append([pos]) 78 | 79 | for arrs in pos_x: 80 | arrs.sort(key=lambda x:x.x) 81 | for arrs in pos_y: 82 | arrs.sort(key=lambda x:x.y) 83 | 84 | self.pitchx = pos_x[0][1].x - pos_x[0][0].x 85 | for arrs in pos_x: 86 | for i in range(len(arrs)): 87 | if i > 0: 88 | pitch = arrs[i].x - arrs[i-1].x 89 | if pitch > 0 and pitch < self.pitchx: 90 | self.pitchx = pitch 91 | 92 | self.pitchy = pos_y[0][1].y - pos_y[0][0].y 93 | for arrs in pos_y: 94 | for i in range(len(arrs)): 95 | if i > 0: 96 | pitch = arrs[i].y - arrs[i-1].y 97 | if pitch > 0 and pitch < self.pitchy: 98 | self.pitchy = pitch 99 | IU_PER_MM = 1000000 100 | px = round(self.pitchx/IU_PER_MM, 4) 101 | py = round(self.pitchy/IU_PER_MM, 4) 102 | if self.logger is not None: 103 | self.logger.info('pitch x: %f mm' %px) 104 | self.logger.info('pitch y: %f mm' %py) 105 | """ 106 | for ind, arrs in enumerate(pos_y): 107 | self.logger.info('%d. sort---------------------' %ind) 108 | for i, arr in enumerate(arrs): 109 | self.logger.info('%d. %s' %(i, str(arr))) 110 | """ 111 | self.footprint.SetOrientationDegrees(self.degrees) 112 | """ 113 | if self.degrees in [0.0 , 90.0, 180.0, -90]: 114 | x = (minx + maxx)/2 115 | y = (miny + maxy)/2 116 | xstart = pcbnew.wxPoint(x, maxy) 117 | xend = pcbnew.wxPoint(x, miny) 118 | ystart = pcbnew.wxPoint(minx, y) 119 | yend = pcbnew.wxPoint(maxx, y) 120 | xtrack = pcbnew.PCB_TRACK(self.board) 121 | xtrack.SetStart(xstart) 122 | xtrack.SetEnd(xend) 123 | xtrack.SetWidth(self.track) 124 | xtrack.SetLayer(pcbnew.F_Cu) 125 | self.board.Add(xtrack) 126 | 127 | ytrack = pcbnew.PCB_TRACK(self.board) 128 | ytrack.SetStart(ystart) 129 | ytrack.SetEnd(yend) 130 | ytrack.SetWidth(self.track) 131 | ytrack.SetLayer(pcbnew.F_Cu) 132 | self.board.Add(ytrack) 133 | else: 134 | anphalx = (-1)*math.tan(self.radian) 135 | anphaly = 1/math.tan(self.radian) 136 | bx = self.y0 - anphalx*self.x0 137 | by = self.y0 - anphaly*self.x0 138 | 139 | # y = ax + b 140 | xyminx = anphalx*minx + bx 141 | xymaxx = anphalx*maxx + bx 142 | xstart = pcbnew.wxPoint(minx, xyminx) 143 | xend = pcbnew.wxPoint(maxx, xymaxx) 144 | 145 | yyminx = anphaly*minx + by 146 | yymaxx = anphaly*maxx + by 147 | ystart = pcbnew.wxPoint(minx, yyminx) 148 | yend = pcbnew.wxPoint(maxx, yymaxx) 149 | 150 | xtrack = pcbnew.PCB_TRACK(self.board) 151 | xtrack.SetStart(xstart) 152 | xtrack.SetEnd(xend) 153 | xtrack.SetWidth(self.track) 154 | xtrack.SetLayer(pcbnew.F_Cu) 155 | self.board.Add(xtrack) 156 | 157 | ytrack = pcbnew.PCB_TRACK(self.board) 158 | ytrack.SetStart(ystart) 159 | ytrack.SetEnd(yend) 160 | ytrack.SetWidth(self.track) 161 | ytrack.SetLayer(pcbnew.F_Cu) 162 | self.board.Add(ytrack) 163 | ####### 164 | anx = -1*math.tan(self.radian_pad) 165 | any = 1/math.tan(self.radian_pad) 166 | b1 = self.y0 - anx*self.x0 167 | b2 = self.y0 - any*self.x0 168 | y1 = anx*minx + b1 169 | y2 = anx*maxx + b1 170 | 171 | y3 = any*minx + b2 172 | y4 = any*maxx + b2 173 | start1 = pcbnew.wxPoint(minx, y1) 174 | end1 = pcbnew.wxPoint(maxx, y2) 175 | 176 | start2 = pcbnew.wxPoint(minx, y3) 177 | end2 = pcbnew.wxPoint(maxx, y4) 178 | 179 | track1 = pcbnew.PCB_TRACK(self.board) 180 | track1.SetStart(start1) 181 | track1.SetEnd(end1) 182 | track1.SetWidth(self.track) 183 | track1.SetLayer(pcbnew.F_Cu) 184 | self.board.Add(track1) 185 | 186 | track2 = pcbnew.PCB_TRACK(self.board) 187 | track2.SetStart(start2) 188 | track2.SetEnd(end2) 189 | track2.SetWidth(self.track) 190 | track2.SetLayer(pcbnew.F_Cu) 191 | self.board.Add(track2) 192 | pcbnew.Refresh() 193 | """ 194 | import math 195 | 196 | def get_xy_extremum(self, rotation_angle=0): 197 | pos_list = [] 198 | for pad in self.pads: 199 | # 获取焊盘位置 200 | position = pad.GetPosition() 201 | # 计算旋转后的坐标 202 | rotated_x = position.x * math.cos(rotation_angle) - position.y * math.sin(rotation_angle) 203 | rotated_y = position.x * math.sin(rotation_angle) + position.y * math.cos(rotation_angle) 204 | pos_list.append((rotated_x, rotated_y)) # 存储为元组 205 | 206 | min_x = min(pos_list, key=lambda x: x[0])[0] 207 | max_x = max(pos_list, key=lambda x: x[0])[0] 208 | min_y = min(pos_list, key=lambda x: x[1])[1] 209 | max_y = max(pos_list, key=lambda x: x[1])[1] 210 | 211 | return min_x, max_x, min_y, max_y 212 | 213 | def skip_pads(self, pos, min_x, max_x, min_y, max_y, pad, rotation_angle=0): 214 | # 计算旋转坐标 215 | rotated_x = pos.x * math.cos(rotation_angle) - pos.y * math.sin(rotation_angle) 216 | rotated_y = pos.x * math.sin(rotation_angle) + pos.y * math.cos(rotation_angle) 217 | 218 | x_distance = min(rotated_x - min_x, max_x - rotated_x) 219 | y_distance = min(rotated_y - min_y, max_y - rotated_y) 220 | n_ring = min(x_distance, y_distance) / self.pitchx 221 | net_name = pad.GetNet().GetNetname() 222 | 223 | if n_ring < self.skip or (("unconnected" in net_name) and not self.unused_pads): 224 | return True 225 | else: 226 | return False 227 | 228 | 229 | def fanout(self): 230 | if self.alignment == 'Quadrant': 231 | if self.degrees in [0.0 , 90.0, 180.0, -90.0]: 232 | self.quadrant_0_90_180() 233 | elif self.degrees in [45.0 , 135.0, -135.0, -45.0]: 234 | self.quadrant_45_135() 235 | else: 236 | self.quadrant_other_angle() 237 | elif self.alignment == 'Diagonal': 238 | if self.degrees in [0.0 , 90.0, 180.0, -90.0]: 239 | self.diagonal_0_90_180() 240 | elif self.degrees in [45.0 , 135.0, -135.0, -45.0]: 241 | self.diagonal_45_135() 242 | else: 243 | self.diagonal_other_angle() 244 | elif self.alignment == 'X-pattern': 245 | if self.degrees in [0.0 , 90.0, 180.0, -90.0]: 246 | self.xpattern_0_90_180() 247 | elif self.degrees in [45.0 , 135.0, -135.0, -45.0]: 248 | self.xpattern_45_135() 249 | else: 250 | self.xpattern_other_angle() 251 | 252 | pcbnew.Refresh() 253 | 254 | # quadrant 255 | def quadrant_0_90_180(self): 256 | min_x, max_x, min_y, max_y = self.get_xy_extremum() 257 | for pad in self.pads: 258 | pos = pad.GetPosition() 259 | net = pad.GetNetCode() 260 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad): 261 | continue 262 | if pos.y > self.y0: 263 | if pos.x > self.x0: 264 | # bottom-right 225 265 | x = pos.x + self.pitchx/2 266 | y = pos.y + self.pitchy/2 267 | else: 268 | # bottom-left 135 269 | x = pos.x - self.pitchx/2 270 | y = pos.y + self.pitchy/2 271 | point = pcbnew.wxPoint(x, y) 272 | end = pcbnew.VECTOR2I(point.x, point.y) 273 | self.add_track(net, pos, end) 274 | self.add_via(net, end) 275 | else: 276 | if pos.x > self.x0: 277 | # top-right 315 278 | x = pos.x + self.pitchx/2 279 | y = pos.y - self.pitchy/2 280 | else: 281 | # top-left 45 282 | x = pos.x - self.pitchx/2 283 | y = pos.y - self.pitchy/2 284 | point = pcbnew.wxPoint(x, y) 285 | end = pcbnew.VECTOR2I(point.x, point.y) 286 | self.add_track(net, pos, end) 287 | self.add_via(net, end) 288 | 289 | def quadrant_45_135(self): 290 | bx = self.y0 + self.x0 291 | by = self.y0 - self.x0 292 | pitch = math.sqrt(self.pitchx*self.pitchx + self.pitchy*self.pitchy)/2 293 | min_x, max_x, min_y, max_y = self.get_xy_extremum(45/180*math.pi) 294 | for pad in self.pads: 295 | pos = pad.GetPosition() 296 | net = pad.GetNetCode() 297 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad, 45/180*math.pi): 298 | continue 299 | y1 = bx - pos.x 300 | y2 = by + pos.x 301 | if pos.y > y1: 302 | if pos.y > y2: 303 | # bottom 304 | x = pos.x 305 | y = pos.y + pitch 306 | else: 307 | # left 308 | x = pos.x + pitch 309 | y = pos.y 310 | #end = pcbnew.wxPoint(x, y) 311 | point = pcbnew.wxPoint(x, y) 312 | end = pcbnew.VECTOR2I(point.x, point.y) 313 | self.add_track(net, pos, end) 314 | self.add_via(net, end) 315 | else: 316 | if pos.y > y2: 317 | # right 318 | x = pos.x - pitch 319 | y = pos.y 320 | else: 321 | # top 322 | x = pos.x 323 | y = pos.y - pitch 324 | #end = pcbnew.wxPoint(x, y) 325 | point = pcbnew.wxPoint(x, y) 326 | end = pcbnew.VECTOR2I(point.x, point.y) 327 | self.add_track(net, pos, end) 328 | self.add_via(net, end) 329 | 330 | def quadrant_other_angle(self): 331 | #anphalx = (-1)*math.tan(self.radian) 332 | #anphaly = 1/math.tan(self.radian) 333 | anphalx = (-1)*self.radian.Tan() 334 | anphaly = 1/self.radian.Tan() 335 | bx0 = self.y0 - anphalx*self.x0 336 | by0 = self.y0 - anphaly*self.x0 337 | 338 | #pax = -1*math.tan(self.radian_pad) 339 | #pay = 1/math.tan(self.radian_pad) 340 | pax = -1*self.radian_pad.Tan() 341 | pay = 1/self.radian_pad.Tan() 342 | pitch = math.sqrt(self.pitchx*self.pitchx + self.pitchy*self.pitchy)/2 343 | angle_radian = self.degrees/180*math.pi 344 | min_x, max_x, min_y, max_y = self.get_xy_extremum(angle_radian) 345 | for pad in self.pads: 346 | pos = pad.GetPosition() 347 | net = pad.GetNetCode() 348 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad, angle_radian): 349 | continue 350 | y1 = anphalx*pos.x + bx0 351 | y2 = anphaly*pos.x + by0 352 | pbx = pos.y - pax*pos.x 353 | pby = pos.y - pay*pos.x 354 | 355 | # d^2 = (x - x0)^2 + (y - y0)^2 356 | # = (x - x0)^2 + (a.x + b - y0)^2 357 | # = x^2 - 2x.x0 + x0^2 + a^2.x^2 + a.b.x - a.y0.x + a.b.x + b^2 - b.y0 - a.y0.x - b.y0 + y0^2 358 | # = (1 + a.a)x.x = (-2.x0 + 2.a.b - 2.a.y0)x + (x0.x0 + b.b - 2.b.y0 + y0.y0) - d.d 359 | ax = pax*pax + 1 360 | bx = 2*pax*pbx - 2*pos.x - 2*pax*pos.y 361 | cx = pos.x*pos.x + pbx*pbx + pos.y*pos.y - 2*pbx*pos.y - pitch*pitch 362 | 363 | ay = pay*pay + 1 364 | by = 2*pay*pby - 2*pos.x - 2*pay*pos.y 365 | cy = pos.x*pos.x + pby*pby + pos.y*pos.y - 2*pby*pos.y - pitch*pitch 366 | 367 | deltax = bx*bx - 4*ax*cx 368 | deltay = by*by - 4*ay*cy 369 | if deltax > 0: 370 | x1 = (-(bx) + math.sqrt(deltax))/(2*ax) 371 | x2 = (-(bx) - math.sqrt(deltax))/(2*ax) 372 | if deltay > 0: 373 | x3 = (-(by) + math.sqrt(deltay))/(2*ay) 374 | x4 = (-(by) - math.sqrt(deltay))/(2*ay) 375 | degrees_0to45 = self.degrees > 0 and self.degrees < 45 376 | degrees_45to90 = self.degrees > 45 and self.degrees < 90 377 | degrees_90to135 = self.degrees > 90 and self.degrees < 135 378 | degrees_135to180 =self.degrees > 135 and self.degrees < 180 379 | degrees_0to90 = self.degrees > 0 and self.degrees < 90 380 | degrees_90to180 =self.degrees > 90 and self.degrees < 180 381 | 382 | degrees_n45to0 = self.degrees > -45 and self.degrees < 0 383 | degrees_n90to45 = self.degrees > -90 and self.degrees < -45 384 | degrees_n135to90 = self.degrees > -135 and self.degrees < -90 385 | degrees_n180to135 =self.degrees > -180 and self.degrees < -135 386 | degrees_n180to90 =self.degrees > -180 and self.degrees < -90 387 | degrees_n90to0 = self.degrees > -90 and self.degrees < 0 388 | if pos.y > y1: 389 | x = 0 390 | y = 0 391 | if pos.y > y2: 392 | # bottom-left 393 | if degrees_0to45 or degrees_n180to135: 394 | x = x2 395 | y = pax*x + pbx 396 | elif degrees_45to90 or degrees_n135to90: 397 | x = x1 398 | y = pax*x + pbx 399 | elif degrees_90to135 or degrees_n90to45: 400 | x = x4 401 | y = pay*x + pby 402 | elif degrees_135to180 or degrees_n45to0: 403 | x = x3 404 | y = pay*x + pby 405 | 406 | else: 407 | # bottom-right 408 | if degrees_0to90 or degrees_n180to90: 409 | x = x3 410 | y = pay*x + pby 411 | elif degrees_90to180 or degrees_n90to0: 412 | x = x2 413 | y = pax*x + pbx 414 | #end = pcbnew.wxPoint(x, y) 415 | point = pcbnew.wxPoint(x, y) 416 | end = pcbnew.VECTOR2I(point.x, point.y) 417 | self.add_track(net, pos, end) 418 | self.add_via(net, end) 419 | else: 420 | x = 0 421 | y = 0 422 | if pos.y > y2: 423 | # top-left 424 | if degrees_0to90 or degrees_n180to90: 425 | x = x4 426 | y = pay*x + pby 427 | elif degrees_90to180 or degrees_n90to0: 428 | x = x1 429 | y = pax*x + pbx 430 | else: 431 | # bottom-right 432 | if degrees_0to45 or degrees_n180to135: 433 | x = x1 434 | y = pax*x + pbx 435 | elif degrees_45to90 or degrees_n135to90: 436 | x = x2 437 | y = pax*x + pbx 438 | elif degrees_90to135 or degrees_n90to45: 439 | x = x3 440 | y = pay*x + pby 441 | elif degrees_135to180 or degrees_n45to0: 442 | x = x4 443 | y = pay*x + pby 444 | #end = pcbnew.wxPoint(x, y) 445 | point = pcbnew.wxPoint(x, y) 446 | end = pcbnew.VECTOR2I(point.x, point.y) 447 | self.add_track(net, pos, end) 448 | self.add_via(net, end) 449 | 450 | # diagonal 451 | def diagonal_0_90_180(self): 452 | min_x, max_x, min_y, max_y = self.get_xy_extremum() 453 | for pad in self.pads: 454 | pos = pad.GetPosition() 455 | net = pad.GetNetCode() 456 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad): 457 | continue 458 | x = 0 459 | y = 0 460 | if self.direction =='TopLeft': 461 | x = pos.x - self.pitchx/2 462 | y = pos.y - self.pitchy/2 463 | if self.direction =='TopRight': 464 | x = pos.x + self.pitchx/2 465 | y = pos.y - self.pitchy/2 466 | if self.direction =='BottomLeft': 467 | x = pos.x - self.pitchx/2 468 | y = pos.y + self.pitchy/2 469 | if self.direction =='BottomRight': 470 | x = pos.x + self.pitchx/2 471 | y = pos.y + self.pitchy/2 472 | #end = pcbnew.wxPoint(x, y) 473 | point = pcbnew.wxPoint(x, y) 474 | end = pcbnew.VECTOR2I(point.x, point.y) 475 | self.add_track(net, pos, end) 476 | self.add_via(net, end) 477 | 478 | def diagonal_45_135(self): 479 | pitch = math.sqrt(self.pitchx*self.pitchx + self.pitchy*self.pitchy)/2 480 | angle_radian = 45/180*math.pi 481 | min_x, max_x, min_y, max_y = self.get_xy_extremum(angle_radian) 482 | for pad in self.pads: 483 | pos = pad.GetPosition() 484 | net = pad.GetNetCode() 485 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad, angle_radian): 486 | continue 487 | x = pos.x 488 | y = pos.y 489 | if self.direction =='TopLeft': 490 | x = pos.x - pitch 491 | y = pos.y 492 | if self.direction =='TopRight': 493 | x = pos.x + pitch 494 | y = pos.y 495 | if self.direction =='BottomLeft': 496 | x = pos.x 497 | y = pos.y + pitch 498 | if self.direction =='BottomRight': 499 | x = pos.x 500 | y = pos.y - pitch 501 | #end = pcbnew.wxPoint(x, y) 502 | point = pcbnew.wxPoint(x, y) 503 | end = pcbnew.VECTOR2I(point.x, point.y) 504 | self.add_track(net, pos, end) 505 | self.add_via(net, end) 506 | 507 | def diagonal_other_angle(self): 508 | #pax = -1*math.tan(self.radian_pad) 509 | #pay = 1/math.tan(self.radian_pad) 510 | pax = (-1)*self.radian_pad.Tan() 511 | pay = 1/self.radian_pad.Tan() 512 | pitch = math.sqrt(self.pitchx*self.pitchx + self.pitchy*self.pitchy)/2 513 | angle_radian = self.degrees/180*math.pi 514 | min_x, max_x, min_y, max_y = self.get_xy_extremum(angle_radian) 515 | for pad in self.pads: 516 | pos = pad.GetPosition() 517 | net = pad.GetNetCode() 518 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad, angle_radian): 519 | continue 520 | pbx = pos.y - pax*pos.x 521 | pby = pos.y - pay*pos.x 522 | 523 | # d^2 = (x - x0)^2 + (y - y0)^2 524 | # = (x - x0)^2 + (a.x + b - y0)^2 525 | # = x^2 - 2x.x0 + x0^2 + a^2.x^2 + a.b.x - a.y0.x + a.b.x + b^2 - b.y0 - a.y0.x - b.y0 + y0^2 526 | # = (1 + a.a)x.x = (-2.x0 + 2.a.b - 2.a.y0)x + (x0.x0 + b.b - 2.b.y0 + y0.y0) - d.d 527 | ax = pax*pax + 1 528 | bx = 2*pax*pbx - 2*pos.x - 2*pax*pos.y 529 | cx = pos.x*pos.x + pbx*pbx + pos.y*pos.y - 2*pbx*pos.y - pitch*pitch 530 | 531 | ay = pay*pay + 1 532 | by = 2*pay*pby - 2*pos.x - 2*pay*pos.y 533 | cy = pos.x*pos.x + pby*pby + pos.y*pos.y - 2*pby*pos.y - pitch*pitch 534 | 535 | deltax = bx*bx - 4*ax*cx 536 | deltay = by*by - 4*ay*cy 537 | if deltax > 0: 538 | x1 = (-(bx) + math.sqrt(deltax))/(2*ax) 539 | x2 = (-(bx) - math.sqrt(deltax))/(2*ax) 540 | if deltay > 0: 541 | x3 = (-(by) + math.sqrt(deltay))/(2*ay) 542 | x4 = (-(by) - math.sqrt(deltay))/(2*ay) 543 | x = pos.x 544 | y = pos.y 545 | if self.direction =='TopLeft': 546 | x = x4 547 | y = pay*x + pby 548 | if self.direction =='TopRight': 549 | x = x2 550 | y = pax*x + pbx 551 | if self.direction =='BottomLeft': 552 | x = x1 553 | y = pax*x + pbx 554 | if self.direction =='BottomRight': 555 | x = x3 556 | y = pay*x + pby 557 | #end = pcbnew.wxPoint(x, y) 558 | point = pcbnew.wxPoint(x, y) 559 | end = pcbnew.VECTOR2I(point.x, point.y) 560 | self.add_track(net, pos, end) 561 | self.add_via(net, end) 562 | 563 | #X-pattern 564 | def xpattern_0_90_180(self): 565 | bx = self.y0 + self.x0 566 | by = self.y0 - self.x0 567 | min_x, max_x, min_y, max_y = self.get_xy_extremum() 568 | for pad in self.pads: 569 | pos = pad.GetPosition() 570 | net = pad.GetNetCode() 571 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad): 572 | continue 573 | y1 = bx - pos.x 574 | y2 = by + pos.x 575 | x = 0 576 | y = 0 577 | if pos.y > y1: 578 | if pos.y > y2: 579 | #bottom 580 | if self.direction =='Counterclock': 581 | x = pos.x - self.pitchx/2 582 | y = pos.y + self.pitchy/2 583 | if self.direction =='Counterclockwise': 584 | x = pos.x + self.pitchx/2 585 | y = pos.y + self.pitchy/2 586 | else: 587 | #right 588 | if self.direction =='Counterclock': 589 | x = pos.x + self.pitchx/2 590 | y = pos.y + self.pitchy/2 591 | if self.direction =='Counterclockwise': 592 | x = pos.x + self.pitchx/2 593 | y = pos.y - self.pitchy/2 594 | else: 595 | if pos.y > y2: 596 | #left 597 | if self.direction =='Counterclock': 598 | x = pos.x - self.pitchx/2 599 | y = pos.y - self.pitchy/2 600 | if self.direction =='Counterclockwise': 601 | x = pos.x - self.pitchx/2 602 | y = pos.y + self.pitchy/2 603 | else: 604 | #top 605 | if self.direction =='Counterclock': 606 | x = pos.x + self.pitchx/2 607 | y = pos.y - self.pitchy/2 608 | if self.direction =='Counterclockwise': 609 | x = pos.x - self.pitchx/2 610 | y = pos.y - self.pitchy/2 611 | #end = pcbnew.wxPoint(x, y) 612 | point = pcbnew.wxPoint(x, y) 613 | end = pcbnew.VECTOR2I(point.x, point.y) 614 | self.add_track(net, pos, end) 615 | self.add_via(net, end) 616 | 617 | def xpattern_45_135(self): 618 | pitch = math.sqrt(self.pitchx*self.pitchx + self.pitchy*self.pitchy)/2 619 | angle_radian = 45/180*math.pi 620 | min_x, max_x, min_y, max_y = self.get_xy_extremum(angle_radian) 621 | for pad in self.pads: 622 | pos = pad.GetPosition() 623 | net = pad.GetNetCode() 624 | if self.skip_pads(pos, min_x, max_x, min_y, max_y,pad,angle_radian): 625 | continue 626 | x = 0 627 | y = 0 628 | if pos.y > self.y0: 629 | if pos.x > self.x0: 630 | #bottom-right 631 | if self.direction =='Counterclock': 632 | x = pos.x 633 | y = pos.y + pitch 634 | if self.direction =='Counterclockwise': 635 | x = pos.x + pitch 636 | y = pos.y 637 | else: 638 | #bottom-left 639 | if self.direction =='Counterclock': 640 | x = pos.x - pitch 641 | y = pos.y 642 | if self.direction =='Counterclockwise': 643 | x = pos.x 644 | y = pos.y + pitch 645 | else: 646 | if pos.x > self.x0: 647 | #bottom-right 648 | if self.direction =='Counterclock': 649 | x = pos.x + pitch 650 | y = pos.y 651 | if self.direction =='Counterclockwise': 652 | x = pos.x 653 | y = pos.y - pitch 654 | else: 655 | #bottom-left 656 | if self.direction =='Counterclock': 657 | x = pos.x 658 | y = pos.y - pitch 659 | if self.direction =='Counterclockwise': 660 | x = pos.x - pitch 661 | y = pos.y 662 | 663 | #end = pcbnew.wxPoint(x, y) 664 | point = pcbnew.wxPoint(x, y) 665 | end = pcbnew.VECTOR2I(point.x, point.y) 666 | self.add_track(net, pos, end) 667 | self.add_via(net, end) 668 | 669 | def add_track(self, net, start, end): 670 | track = pcbnew.PCB_TRACK(self.board) 671 | track.SetStart(start) 672 | track.SetEnd(end) 673 | track.SetWidth(self.track) 674 | track.SetLayer(pcbnew.F_Cu) 675 | track.SetNetCode(net) 676 | self.board.Add(track) 677 | self.tracks.append(track) 678 | 679 | self.group.SetName("FANOUT_TRACKS_and_VIA") 680 | self.group.AddItem(track) 681 | self.board.Add(self.group) 682 | # self.groups.append(self.group) 683 | 684 | 685 | def add_via(self, net, pos): 686 | via = pcbnew.PCB_VIA(self.board) 687 | via.SetViaType(pcbnew.VIATYPE_THROUGH) 688 | if self.get_major_version() >= 7: 689 | # KiCad v7 690 | via.SetPosition(pcbnew.VECTOR2I(pos)) 691 | else: 692 | # KiCad v6 693 | via.SetPosition(pos) 694 | via.SetWidth(int(self.via.m_Diameter)) 695 | via.SetDrill(self.via.m_Drill) 696 | via.SetNetCode(net) 697 | self.board.Add(via) 698 | self.tracks.append(via) 699 | 700 | self.group.AddItem(via) 701 | self.board.Add(self.group) 702 | 703 | def remove_track_via(self): 704 | for item in self.tracks: 705 | self.board.Remove(item) 706 | self.tracks.clear() 707 | pcbnew.Refresh() -------------------------------------------------------------------------------- /onekiwi/model/model.py: -------------------------------------------------------------------------------- 1 | import pcbnew 2 | from ..kicad.board import get_current_unit 3 | from .bga import BGA 4 | 5 | class Model: 6 | def __init__(self, board, logger): 7 | self.logger = logger 8 | self.unit = get_current_unit() 9 | self.board = board 10 | self.references = [] 11 | self.reference = None 12 | self.skip = 0 13 | self.track = None 14 | self.via = None 15 | self.unused_pads=None 16 | self.package = None 17 | self.alignment = None 18 | self.direction = None 19 | self.indexSelected = None 20 | self.update_reference() 21 | 22 | 23 | def update_reference(self): 24 | footprints = self.board.GetFootprints() 25 | for index,footprint in enumerate(footprints): 26 | if footprint.IsSelected(): 27 | self.indexSelected = index 28 | ref = str(footprint.GetReference()) 29 | self.references.append(ref) 30 | 31 | def update_data(self, reference,skip, track, via, unused_pads): 32 | self.reference = reference 33 | self.skip = skip 34 | self.track = track 35 | self.via = via 36 | self.unused_pads = unused_pads 37 | 38 | def update_package(self, package, alignment, direction): 39 | self.package = package 40 | self.alignment = alignment 41 | self.direction = direction 42 | 43 | def fanout(self): 44 | if self.package == 'BGA': 45 | self.bga = BGA( 46 | self.board, self.reference,self.skip, self.track, self.via, self.unused_pads, 47 | self.alignment, self.direction, self.logger 48 | ) 49 | self.bga.fanout() 50 | 51 | def remove_track_via(self): 52 | self.bga.remove_track_via() 53 | 54 | 55 | -------------------------------------------------------------------------------- /onekiwi/plugin.py: -------------------------------------------------------------------------------- 1 | import pcbnew 2 | import os 3 | from .controller.controller import Controller 4 | 5 | class FanoutAction(pcbnew.ActionPlugin): 6 | def defaults(self): 7 | self.name = "Fanout Tools" 8 | self.category = "Modify PCB" 9 | self.description = "BGA fanout routing" 10 | self.show_toolbar_button = True # Optional, defaults to False 11 | self.icon_file_name = os.path.join(os.path.dirname(__file__), 'icon.png') # Optional 12 | 13 | def Run(self): 14 | # The entry function of the plugin that is executed on user action 15 | board = pcbnew.GetBoard() 16 | controller = Controller(board) 17 | controller.Show() 18 | pcbnew.UpdateUserInterface() 19 | FanoutAction().register() # Instantiate and register to Pcbnew 20 | 21 | -------------------------------------------------------------------------------- /onekiwi/version.py: -------------------------------------------------------------------------------- 1 | # Update this when new version is tagged. 2 | import os 3 | import subprocess 4 | 5 | LAST_TAG = '1.1.3' 6 | 7 | def _get_git_version(): 8 | plugin_path = os.path.realpath(os.path.dirname(__file__)) 9 | try: 10 | git_version = subprocess.check_output( 11 | ['git', 'describe', '--tags', '--abbrev=4', '--dirty=-*'], 12 | cwd=plugin_path) 13 | if isinstance(git_version, bytes): 14 | return git_version.decode('utf-8').rstrip() 15 | else: 16 | return git_version.rstrip() 17 | except subprocess.CalledProcessError as e: 18 | print('Git version check failed: ' + str(e)) 19 | except Exception as e: 20 | print('Git process cannot be launched: ' + str(e)) 21 | return None 22 | 23 | version = _get_git_version() or LAST_TAG -------------------------------------------------------------------------------- /onekiwi/view/dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ########################################################################### 4 | ## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) 5 | ## http://www.wxformbuilder.org/ 6 | ## 7 | ## PLEASE DO *NOT* EDIT THIS FILE! 8 | ########################################################################### 9 | 10 | import wx 11 | import wx.xrc 12 | 13 | ########################################################################### 14 | ## Class FanoutDialog 15 | ########################################################################### 16 | 17 | class FanoutDialog ( wx.Dialog ): 18 | 19 | def __init__( self, parent ): 20 | wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"Fanout Tools", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER ) 21 | 22 | self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) 23 | 24 | bSizer1 = wx.BoxSizer( wx.VERTICAL ) 25 | 26 | bSizer5 = wx.BoxSizer( wx.VERTICAL ) 27 | 28 | bSizer8 = wx.BoxSizer( wx.HORIZONTAL ) 29 | 30 | sbSizer5 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Create fanouts" ), wx.VERTICAL ) 31 | 32 | fgSizer1 = wx.FlexGridSizer( 0, 2, 0, 0 ) 33 | fgSizer1.SetFlexibleDirection( wx.VERTICAL ) 34 | fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) 35 | 36 | self.textSkip = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Skip:", wx.DefaultPosition, wx.DefaultSize, 0 ) 37 | self.textSkip.Wrap( -1 ) 38 | fgSizer1.Add( self.textSkip, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 39 | self.skip = wx.SpinCtrlDouble( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 10000, 2, 1 ) 40 | self.skip.SetDigits( 0 ) 41 | fgSizer1.Add( self.skip, 0, wx.ALL|wx.EXPAND, 5 ) 42 | 43 | 44 | self.textFilttter = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Filtter:", wx.DefaultPosition, wx.DefaultSize, 0 ) 45 | self.textFilttter.Wrap( -1 ) 46 | 47 | fgSizer1.Add( self.textFilttter, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) 48 | 49 | self.editFiltter = wx.TextCtrl( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 50 | fgSizer1.Add( self.editFiltter, 0, wx.ALL|wx.EXPAND, 5 ) 51 | 52 | self.textReference = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Reference:", wx.DefaultPosition, wx.DefaultSize, 0 ) 53 | self.textReference.Wrap( -1 ) 54 | 55 | fgSizer1.Add( self.textReference, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 56 | 57 | choiceReferenceChoices = [] 58 | self.choiceReference = wx.Choice( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceReferenceChoices, 0 ) 59 | self.choiceReference.SetSelection( 0 ) 60 | fgSizer1.Add( self.choiceReference, 1, wx.ALL|wx.EXPAND, 5 ) 61 | 62 | self.textTrack = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Track width:", wx.DefaultPosition, wx.DefaultSize, 0 ) 63 | self.textTrack.Wrap( -1 ) 64 | 65 | fgSizer1.Add( self.textTrack, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 66 | 67 | choiceTrackChoices = [] 68 | self.choiceTrack = wx.Choice( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceTrackChoices, 0 ) 69 | self.choiceTrack.SetSelection( 0 ) 70 | fgSizer1.Add( self.choiceTrack, 1, wx.ALL|wx.EXPAND, 5 ) 71 | 72 | self.textVia = wx.StaticText( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Via size:", wx.DefaultPosition, wx.DefaultSize, 0 ) 73 | self.textVia.Wrap( -1 ) 74 | 75 | fgSizer1.Add( self.textVia, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 76 | 77 | choiceViaChoices = [] 78 | self.choiceVia = wx.Choice( sbSizer5.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceViaChoices, 0 ) 79 | self.choiceVia.SetSelection( 0 ) 80 | fgSizer1.Add( self.choiceVia, 1, wx.ALL|wx.EXPAND, 5 ) 81 | 82 | 83 | sbSizer5.Add( fgSizer1, 1, wx.EXPAND, 5 ) 84 | 85 | self.checkUnusepad = wx.CheckBox( sbSizer5.GetStaticBox(), wx.ID_ANY, u"Unused pads", wx.DefaultPosition, wx.DefaultSize, 0 ) 86 | sbSizer5.Add( self.checkUnusepad, 0, wx.ALL, 5 ) 87 | 88 | 89 | bSizer8.Add( sbSizer5, 3, wx.BOTTOM|wx.EXPAND|wx.RIGHT, 5 ) 90 | 91 | sbSizer6 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Preview" ), wx.VERTICAL ) 92 | 93 | self.bitmapPreview = wx.StaticBitmap( sbSizer6.GetStaticBox(), wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, 0 ) 94 | sbSizer6.Add( self.bitmapPreview, 0, wx.ALL|wx.EXPAND, 5 ) 95 | 96 | 97 | bSizer8.Add( sbSizer6, 2, wx.BOTTOM|wx.EXPAND|wx.LEFT, 5 ) 98 | 99 | 100 | bSizer5.Add( bSizer8, 1, wx.EXPAND, 5 ) 101 | 102 | sbSizer7 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Fanout length" ), wx.HORIZONTAL ) 103 | 104 | self.checkUnlimited = wx.CheckBox( sbSizer7.GetStaticBox(), wx.ID_ANY, u"Unlimited", wx.DefaultPosition, wx.DefaultSize, 0 ) 105 | sbSizer7.Add( self.checkUnlimited, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 106 | 107 | self.textMaximum = wx.StaticText( sbSizer7.GetStaticBox(), wx.ID_ANY, u"Maximum:", wx.DefaultPosition, wx.DefaultSize, 0 ) 108 | self.textMaximum.Wrap( -1 ) 109 | 110 | sbSizer7.Add( self.textMaximum, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 111 | 112 | self.editLength = wx.TextCtrl( sbSizer7.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) 113 | sbSizer7.Add( self.editLength, 0, wx.ALL, 5 ) 114 | 115 | self.textUnit = wx.StaticText( sbSizer7.GetStaticBox(), wx.ID_ANY, u"unit", wx.DefaultPosition, wx.DefaultSize, 0 ) 116 | self.textUnit.Wrap( -1 ) 117 | 118 | sbSizer7.Add( self.textUnit, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 119 | 120 | 121 | bSizer5.Add( sbSizer7, 0, wx.EXPAND, 5 ) 122 | 123 | sbSizer8 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"Placement of via fanout for:" ), wx.VERTICAL ) 124 | 125 | bSizer9 = wx.BoxSizer( wx.HORIZONTAL ) 126 | 127 | self.textPackage = wx.StaticText( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Package:", wx.DefaultPosition, wx.DefaultSize, 0 ) 128 | self.textPackage.Wrap( -1 ) 129 | 130 | bSizer9.Add( self.textPackage, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 131 | 132 | choicePackageChoices = [] 133 | self.choicePackage = wx.Choice( sbSizer8.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choicePackageChoices, 0 ) 134 | self.choicePackage.SetSelection( 0 ) 135 | bSizer9.Add( self.choicePackage, 0, wx.ALL, 5 ) 136 | 137 | self.checkSpecial = wx.CheckBox( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Special package:", wx.DefaultPosition, wx.DefaultSize, 0 ) 138 | bSizer9.Add( self.checkSpecial, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) 139 | 140 | choiceSpecialChoices = [] 141 | self.choiceSpecial = wx.Choice( sbSizer8.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceSpecialChoices, 0 ) 142 | self.choiceSpecial.SetSelection( 0 ) 143 | bSizer9.Add( self.choiceSpecial, 0, wx.ALL, 5 ) 144 | 145 | 146 | sbSizer8.Add( bSizer9, 0, wx.EXPAND, 5 ) 147 | 148 | bSizer10 = wx.BoxSizer( wx.HORIZONTAL ) 149 | 150 | sizerAlignment = wx.BoxSizer( wx.VERTICAL ) 151 | 152 | self.textAlignment = wx.StaticText( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Alignment:", wx.DefaultPosition, wx.DefaultSize, 0 ) 153 | self.textAlignment.Wrap( -1 ) 154 | 155 | sizerAlignment.Add( self.textAlignment, 0, wx.ALL, 5 ) 156 | 157 | choiceAlignmentChoices = [] 158 | self.choiceAlignment = wx.Choice( sbSizer8.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceAlignmentChoices, 0 ) 159 | self.choiceAlignment.SetSelection( 0 ) 160 | sizerAlignment.Add( self.choiceAlignment, 0, wx.ALL|wx.EXPAND, 5 ) 161 | 162 | 163 | bSizer10.Add( sizerAlignment, 1, wx.EXPAND, 5 ) 164 | 165 | sizerDirection = wx.BoxSizer( wx.VERTICAL ) 166 | 167 | self.textDirection = wx.StaticText( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Direction:", wx.DefaultPosition, wx.DefaultSize, 0 ) 168 | self.textDirection.Wrap( -1 ) 169 | 170 | sizerDirection.Add( self.textDirection, 0, wx.ALL, 5 ) 171 | 172 | choiceDirectionChoices = [] 173 | self.choiceDirection = wx.Choice( sbSizer8.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceDirectionChoices, 0 ) 174 | self.choiceDirection.SetSelection( 0 ) 175 | sizerDirection.Add( self.choiceDirection, 0, wx.ALL|wx.EXPAND, 5 ) 176 | 177 | 178 | bSizer10.Add( sizerDirection, 1, wx.EXPAND, 5 ) 179 | 180 | sizerSpace = wx.BoxSizer( wx.VERTICAL ) 181 | 182 | self.textSpace = wx.StaticText( sbSizer8.GetStaticBox(), wx.ID_ANY, u"Spacing:", wx.DefaultPosition, wx.DefaultSize, 0 ) 183 | self.textSpace.Wrap( -1 ) 184 | 185 | sizerSpace.Add( self.textSpace, 0, wx.ALL, 5 ) 186 | 187 | choiceSpaceChoices = [] 188 | self.choiceSpace = wx.Choice( sbSizer8.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, choiceSpaceChoices, 0 ) 189 | self.choiceSpace.SetSelection( 0 ) 190 | sizerSpace.Add( self.choiceSpace, 0, wx.ALL|wx.EXPAND, 5 ) 191 | 192 | 193 | bSizer10.Add( sizerSpace, 1, wx.EXPAND, 5 ) 194 | 195 | 196 | sbSizer8.Add( bSizer10, 1, wx.EXPAND, 5 ) 197 | 198 | 199 | bSizer5.Add( sbSizer8, 0, wx.EXPAND|wx.TOP, 5 ) 200 | 201 | 202 | bSizer1.Add( bSizer5, 1, wx.ALL|wx.EXPAND, 5 ) 203 | 204 | bSizer7 = wx.BoxSizer( wx.HORIZONTAL ) 205 | 206 | self.buttonFanout = wx.Button( self, wx.ID_ANY, u"Fanout", wx.DefaultPosition, wx.DefaultSize, 0 ) 207 | bSizer7.Add( self.buttonFanout, 1, wx.ALL|wx.EXPAND, 5 ) 208 | 209 | self.buttonUndo = wx.Button( self, wx.ID_ANY, u"Undo", wx.DefaultPosition, wx.DefaultSize, 0 ) 210 | bSizer7.Add( self.buttonUndo, 1, wx.ALL|wx.EXPAND, 5 ) 211 | 212 | self.buttonClear = wx.Button( self, wx.ID_ANY, u"Clear log", wx.DefaultPosition, wx.DefaultSize, 0 ) 213 | bSizer7.Add( self.buttonClear, 1, wx.ALL|wx.EXPAND, 5 ) 214 | 215 | self.buttonClose = wx.Button( self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0 ) 216 | bSizer7.Add( self.buttonClose, 1, wx.ALL|wx.EXPAND, 5 ) 217 | 218 | 219 | bSizer1.Add( bSizer7, 0, wx.EXPAND, 5 ) 220 | 221 | self.staticLine = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) 222 | bSizer1.Add( self.staticLine, 0, wx.EXPAND |wx.ALL, 5 ) 223 | 224 | self.textLog = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,100 ), wx.HSCROLL|wx.TE_MULTILINE|wx.TE_READONLY ) 225 | bSizer1.Add( self.textLog, 0, wx.ALL|wx.EXPAND, 5 ) 226 | 227 | 228 | self.SetSizer( bSizer1 ) 229 | self.Layout() 230 | bSizer1.Fit( self ) 231 | 232 | self.Centre( wx.BOTH ) 233 | 234 | def __del__( self ): 235 | pass 236 | 237 | 238 | -------------------------------------------------------------------------------- /onekiwi/view/view.py: -------------------------------------------------------------------------------- 1 | import wx 2 | from .dialog import * 3 | from ..version import version 4 | import os 5 | from ..kicad.board import get_image_path 6 | 7 | class FanoutView(FanoutDialog): 8 | def __init__(self): 9 | FanoutDialog.__init__(self, None) 10 | self.SetTitle('Fanout Tool v%s' % version) 11 | def GetSkipIndex(self): 12 | return int(self.skip.GetValue()) 13 | 14 | def AddReferences(self, references): 15 | self.choiceReference.Append(references) 16 | 17 | def SetIndexReferences(self, index): 18 | self.choiceReference.SetSelection(index) 19 | 20 | def ClearReferences(self): 21 | self.choiceReference.Clear() 22 | 23 | def GetReferenceSelected(self): 24 | index = self.choiceReference.GetSelection() 25 | value = self.choiceReference.GetString(index) 26 | return value 27 | def AddTracksWidth(self, tracks): 28 | self.choiceTrack.Append(tracks) 29 | self.choiceTrack.SetSelection(0) 30 | 31 | def AddViasSize(self, vias): 32 | self.choiceVia.Append(vias) 33 | self.choiceVia.SetSelection(0) 34 | def GetCheckUnusepad(self): 35 | return self.checkUnusepad.GetValue() 36 | 37 | def GetTrackSelectedIndex(self): 38 | return self.choiceTrack.GetSelection() 39 | 40 | def GetViaSelectedIndex(self): 41 | return self.choiceVia.GetSelection() 42 | 43 | def AddPackageType(self, items, index): 44 | self.choicePackage.Append(items) 45 | self.choicePackage.SetSelection(index) 46 | 47 | def GetPackageIndex(self): 48 | index = self.choicePackage.GetSelection() 49 | return index 50 | 51 | def GetPackageValue(self): 52 | index = self.choicePackage.GetSelection() 53 | value = self.choicePackage.GetString(index) 54 | return value 55 | 56 | def AddAlignment(self, items): 57 | self.choiceAlignment.Append(items) 58 | self.choiceAlignment.SetSelection(0) 59 | 60 | def ClearAlignment(self): 61 | self.choiceAlignment.Clear() 62 | 63 | def GetAlignmentIndex(self): 64 | index = self.choiceAlignment.GetSelection() 65 | return index 66 | 67 | def GetAlignmentValue(self): 68 | index = self.choiceAlignment.GetSelection() 69 | value = self.choiceAlignment.GetString(index) 70 | return value 71 | 72 | def AddDirection(self, items): 73 | self.choiceDirection.Append(items) 74 | self.choiceDirection.SetSelection(0) 75 | 76 | def ClearDirection(self): 77 | self.choiceDirection.Clear() 78 | 79 | def GetDirectionIndex(self): 80 | index = self.choiceDirection.GetSelection() 81 | return index 82 | 83 | def GetDirectionValue(self): 84 | index = self.choiceDirection.GetSelection() 85 | print(index) 86 | value = self.choiceDirection.GetString(index) 87 | return value 88 | 89 | def SetImagePreview(self, name): 90 | path = get_image_path() 91 | image = os.path.join(path, name) 92 | self.bitmapPreview.SetBitmap(wx.Bitmap(image)) -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | version=$(git describe --tags --dirty) 4 | name=$(echo fanout-tool-$version.zip) 5 | 6 | echo "Building release $version" 7 | cp metadata.json.template metadata.json 8 | sed -i -e "s/VERSION/$version/g" metadata.json 9 | sed -i '/download_/d' metadata.json 10 | sed -i '/install_size/d' metadata.json 11 | 12 | mkdir resources 13 | cp icon/icon_64x64.png resources/ 14 | mv resources/icon_64x64.png resources/icon.png 15 | 16 | mkdir plugins 17 | cp __init__.py plugins/ 18 | cp -r onekiwi/ plugins/ 19 | 20 | zip -r $name plugins resources metadata.json 21 | 22 | rm -rf plugins 23 | rm -rf resources 24 | 25 | sha=$(sha256sum $name | cut -d' ' -f1) 26 | size=$(du -b $name | cut -f1) 27 | installSize=$(unzip -l $name | tail -1 | xargs | cut -d' ' -f1) 28 | 29 | cp metadata.json.template metadata.json 30 | sed -i -e "s/VERSION/$version/g" metadata.json 31 | sed -i -e "s/SHA256/$sha/g" metadata.json 32 | sed -i -e "s/DOWNLOAD_SIZE/$size/g" metadata.json 33 | sed -i -e "s/INSTALL_SIZE/$installSize/g" metadata.json 34 | 35 | ls -lh $name metadata.json 36 | 37 | --------------------------------------------------------------------------------