├── README.md ├── action_menu_arrange_like_schematic.py └── sch.py /README.md: -------------------------------------------------------------------------------- 1 | # kicad-plugin-arrange-like-schematic 2 | 3 | Copy to `.kicad_plugins`. 4 | 5 | Arranges components as they appear in the schematic to aid initial layout. -------------------------------------------------------------------------------- /action_menu_arrange_like_schematic.py: -------------------------------------------------------------------------------- 1 | import pcbnew 2 | import re 3 | import datetime 4 | import json 5 | 6 | import Tkinter 7 | from tkFileDialog import askopenfilename 8 | 9 | class ArrangeLikeSchematic(pcbnew.ActionPlugin): 10 | def defaults( self ): 11 | self.name = "Arrange like schematic" 12 | self.category = "Modify PCB" 13 | self.description = "Arrange components so that layout is similar to schematic" 14 | 15 | def get_sch_filename(self): 16 | root = Tkinter.Tk() 17 | root.update() 18 | filename = askopenfilename( 19 | initialdir = "~", 20 | title = "Select file", 21 | filetypes = (("sch files","*.sch"),("all files","*.*")), 22 | ) 23 | root.update() 24 | root.destroy() 25 | root.mainloop() 26 | return filename 27 | 28 | def get_component_positions(self, fn): 29 | import sch 30 | positions = {} 31 | s = sch.Schematic(fn) 32 | for c in s.components: 33 | ref = c.labels['ref'] 34 | positions[ref] = c.position 35 | return positions 36 | 37 | def Run( self ): 38 | fn = self.get_sch_filename() 39 | positions = self.get_component_positions(fn) 40 | pcb = pcbnew.GetBoard() 41 | components = pcb.GetModules() 42 | for c in components: 43 | ref = c.GetReference() 44 | if ref in positions: 45 | x, y = int(positions[ref]['posx'])*25.4/1000, int(positions[ref]['posy'])*25.4/1000 46 | c.SetPosition(pcbnew.wxPointMM(x, y)) 47 | 48 | ArrangeLikeSchematic().register() -------------------------------------------------------------------------------- /sch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, shlex 4 | 5 | class Description(object): 6 | """ 7 | A class to parse description information of Schematic Files Format of the KiCad 8 | TODO: Need to be done, currently just stores the raw data read from file 9 | """ 10 | def __init__(self, data): 11 | self.raw_data = data 12 | 13 | class Component(object): 14 | """ 15 | A class to parse components of Schematic Files Format of the KiCad 16 | """ 17 | _L_KEYS = ['name', 'ref'] 18 | _U_KEYS = ['unit', 'convert', 'time_stamp'] 19 | _P_KEYS = ['posx', 'posy'] 20 | _AR_KEYS = ['path', 'ref', 'part'] 21 | _F_KEYS = ['id', 'ref', 'orient', 'posx', 'posy', 'size', 'attributs', 'hjust', 'props', 'name'] 22 | 23 | _KEYS = {'L':_L_KEYS, 'U':_U_KEYS, 'P':_P_KEYS, 'AR':_AR_KEYS, 'F':_F_KEYS} 24 | def __init__(self, data): 25 | self.labels = {} 26 | self.unit = {} 27 | self.position = {} 28 | self.references = [] 29 | self.fields = [] 30 | self.old_stuff = [] 31 | 32 | for line in data: 33 | if line[0] == '\t': 34 | self.old_stuff.append(line) 35 | continue 36 | 37 | line = line.replace('\n', '') 38 | s = shlex.shlex(line) 39 | s.whitespace_split = True 40 | s.commenters = '' 41 | s.quotes = '"' 42 | line = list(s) 43 | 44 | # select the keys list and default values array 45 | if line[0] in self._KEYS: 46 | key_list = self._KEYS[line[0]] 47 | values = line[1:] + ['' for n in range(len(key_list) - len(line[1:]))] 48 | 49 | if line[0] == 'L': 50 | self.labels = dict(zip(key_list,values)) 51 | elif line[0] == 'U': 52 | self.unit = dict(zip(key_list,values)) 53 | elif line[0] == 'P': 54 | self.position = dict(zip(key_list,values)) 55 | elif line[0] == 'AR': 56 | self.references.append(dict(zip(key_list,values))) 57 | elif line[0] == 'F': 58 | self.fields.append(dict(zip(key_list,values))) 59 | 60 | # TODO: error checking 61 | # * check if field_data is a dictionary 62 | # * check if at least 'ref' and 'name' were passed 63 | # * ignore invalid items of field_data on merging 64 | # TODO: enhancements 65 | # * 'value' could be used instead of 'ref' 66 | def addField(self, field_data): 67 | def_field = {'id':None, 'ref':None, 'orient':'H', 'posx':'0', 'posy':'0', 'size':'50', 68 | 'attributs':'0001', 'hjust':'C', 'props':'CNN', 'name':'~'} 69 | 70 | # merge dictionaries and set the id value 71 | field = dict(list(def_field.items()) + list(field_data.items())) 72 | field['id'] = str(len(self.fields)) 73 | 74 | self.fields.append(field) 75 | return field 76 | 77 | class Sheet(object): 78 | """ 79 | A class to parse sheets of Schematic Files Format of the KiCad 80 | """ 81 | _S_KEYS = ['topLeftPosx', 'topLeftPosy','botRightPosx', 'botRightPosy'] 82 | _U_KEYS = ['uniqID'] 83 | _F_KEYS = ['id', 'value', 'IOState', 'side', 'posx', 'posy', 'size'] 84 | 85 | _KEYS = {'S':_S_KEYS, 'U':_U_KEYS, 'F':_F_KEYS} 86 | def __init__(self, data): 87 | self.shape = {} 88 | self.unit = {} 89 | self.fields = [] 90 | for line in data: 91 | line = line.replace('\n', '') 92 | s = shlex.shlex(line) 93 | s.whitespace_split = True 94 | s.commenters = '' 95 | s.quotes = '"' 96 | line = list(s) 97 | # select the keys list and default values array 98 | if line[0] in self._KEYS: 99 | key_list = self._KEYS[line[0]] 100 | values = line[1:] + ['' for n in range(len(key_list) - len(line[1:]))] 101 | if line[0] == 'S': 102 | self.shape = dict(zip(key_list,values)) 103 | elif line[0] == 'U': 104 | self.unit = dict(zip(key_list,values)) 105 | elif line[0][0] == 'F': 106 | key_list = self._F_KEYS 107 | values = line + ['' for n in range(len(key_list) - len(line))] 108 | self.fields.append(dict(zip(key_list,values))) 109 | 110 | class Bitmap(object): 111 | """ 112 | A class to parse bitmaps of Schematic Files Format of the KiCad 113 | TODO: Need to be done, currently just stores the raw data read from file 114 | """ 115 | def __init__(self, data): 116 | self.raw_data = data 117 | 118 | class Schematic(object): 119 | """ 120 | A class to parse Schematic Files Format of the KiCad 121 | """ 122 | def __init__(self, filename): 123 | f = open(filename) 124 | self.filename = filename 125 | self.header = f.readline() 126 | self.libs = [] 127 | self.eelayer = None 128 | self.description = None 129 | self.components = [] 130 | self.sheets = [] 131 | self.bitmaps = [] 132 | self.texts = [] 133 | self.wires = [] 134 | self.entries = [] 135 | self.conns = [] 136 | self.noconns = [] 137 | 138 | if not 'EESchema Schematic File' in self.header: 139 | self.header = None 140 | sys.stderr.write('The file is not a KiCad Schematic File\n') 141 | return 142 | 143 | building_block = False 144 | 145 | while True: 146 | line = f.readline() 147 | if not line: break 148 | 149 | if line.startswith('LIBS:'): 150 | self.libs.append(line) 151 | 152 | elif line.startswith('EELAYER END'): 153 | pass 154 | elif line.startswith('EELAYER'): 155 | self.eelayer = line 156 | 157 | elif not building_block: 158 | if line.startswith('$'): 159 | building_block = True 160 | block_data = [] 161 | block_data.append(line) 162 | elif line.startswith('Text'): 163 | data = {'desc':line, 'data':f.readline()} 164 | self.texts.append(data) 165 | elif line.startswith('Wire'): 166 | data = {'desc':line, 'data':f.readline()} 167 | self.wires.append(data) 168 | elif line.startswith('Entry'): 169 | data = {'desc':line, 'data':f.readline()} 170 | self.entries.append(data) 171 | elif line.startswith('Connection'): 172 | data = {'desc':line} 173 | self.conns.append(data) 174 | elif line.startswith('NoConn'): 175 | data = {'desc':line} 176 | self.noconns.append(data) 177 | 178 | elif building_block: 179 | block_data.append(line) 180 | if line.startswith('$End'): 181 | building_block = False 182 | 183 | if line.startswith('$EndDescr'): 184 | self.description = Description(block_data) 185 | if line.startswith('$EndComp'): 186 | self.components.append(Component(block_data)) 187 | if line.startswith('$EndSheet'): 188 | self.sheets.append(Sheet(block_data)) 189 | if line.startswith('$EndBitmap'): 190 | self.bitmaps.append(Bitmap(block_data)) 191 | 192 | def save(self, filename=None): 193 | # check whether it has header, what means that sch file was loaded fine 194 | if not self.header: return 195 | 196 | if not filename: filename = self.filename 197 | 198 | # insert the header 199 | to_write = [] 200 | to_write += [self.header] 201 | 202 | # LIBS 203 | to_write += self.libs 204 | 205 | # EELAYER 206 | to_write += [self.eelayer, 'EELAYER END\n'] 207 | 208 | # Description 209 | to_write += self.description.raw_data 210 | 211 | # Sheets 212 | for sheet in self.sheets: 213 | to_write += ['$Sheet\n'] 214 | if sheet.shape: 215 | line = 'S ' 216 | for key in sheet._S_KEYS: 217 | line+= sheet.shape[key] + ' ' 218 | to_write += [line.rstrip() + '\n'] 219 | if sheet.unit: 220 | line = 'U ' 221 | for key in sheet._U_KEYS: 222 | line += sheet.unit[key] + ' ' 223 | to_write += [line.rstrip() + '\n'] 224 | 225 | for field in sheet.fields: 226 | line = '' 227 | for key in sheet._F_KEYS: 228 | line += field[key] + ' ' 229 | to_write += [line.rstrip() + '\n'] 230 | to_write += ['$EndSheet\n'] 231 | 232 | # Components 233 | for component in self.components: 234 | to_write += ['$Comp\n'] 235 | if component.labels: 236 | line = 'L ' 237 | for key in component._L_KEYS: 238 | line += component.labels[key] + ' ' 239 | to_write += [line.rstrip() + '\n'] 240 | 241 | if component.unit: 242 | line = 'U ' 243 | for key in component._U_KEYS: 244 | line += component.unit[key] + ' ' 245 | to_write += [line.rstrip() + '\n'] 246 | 247 | if component.position: 248 | line = 'P ' 249 | for key in component._P_KEYS: 250 | line += component.position[key] + ' ' 251 | to_write += [line.rstrip() + '\n'] 252 | 253 | for reference in component.references: 254 | if component.references: 255 | line = 'AR ' 256 | for key in component._AR_KEYS: 257 | line += reference[key] + ' ' 258 | to_write += [line.rstrip() + '\n'] 259 | 260 | for field in component.fields: 261 | line = 'F ' 262 | for key in component._F_KEYS: 263 | line += field[key] + ' ' 264 | to_write += [line.rstrip() + '\n'] 265 | 266 | if component.old_stuff: 267 | to_write += component.old_stuff 268 | 269 | to_write += ['$EndComp\n'] 270 | 271 | # Bitmaps 272 | for bitmap in self.bitmaps: 273 | to_write += bitmap.raw_data 274 | 275 | # Texts 276 | for text in self.texts: 277 | to_write += [text['desc'], text['data']] 278 | 279 | # Wires 280 | for wire in self.wires: 281 | to_write += [wire['desc'], wire['data']] 282 | 283 | # Entries 284 | for entry in self.entries: 285 | to_write += [entry['desc'], entry['data']] 286 | 287 | # Connections 288 | for conn in self.conns: 289 | to_write += [conn['desc']] 290 | 291 | # No Connetions 292 | for noconn in self.noconns: 293 | to_write += [noconn['desc']] 294 | 295 | to_write += ['$EndSCHEMATC\n'] 296 | 297 | f = open(filename, 'w') 298 | f.writelines(to_write) --------------------------------------------------------------------------------