1#!/usr/local/bin/python3.8 2# coding: iso8859-1 3############################################################################ 4# tragesym - create gEDA symbols out of structured textfiles 5# begin : 2001-10-20 6# copyright : (C) 2001,2002,2003,2004,2006,2007, 2008 by Werner Hoch 7# email : werner.ho@gmx.de 8############################################################################ 9# # 10# This program is free software; you can redistribute it and/or modify # 11# it under the terms of the GNU General Public License as published by # 12# the Free Software Foundation; either version 2 of the License, or # 13# (at your option) any later version. # 14# # 15############################################################################ 16 17# FEATURES: 18# - create pins and their elements 19# - sort pins alphabetical 20# - swap words of the pinlabels 21# - negation lines if label is in "_","\" is for escape 22# - rotate top and bottom pinlabels if wished 23# - if the symbol's width specified is 0, then tragesym calculates the 24# symbol width based on the greater number of pins at the top or at the 25# bottom of the symbol 26 27import getopt, os.path, re, string, sys 28 29##################### GLOBALS ############################################ 30VERSION="0.0.15" 31 32CHARHIGH=26 33preset_options = {"wordswap":"yes", 34 "rotate_labels":"no", 35 "sort_labels":"yes", 36 "generate_pinseq":"yes", 37 "sym_width":"1400", 38 "pinwidthvertikal":"400", 39 "pinwidthvertical":"400", 40 "pinwidthhorizontal":"400"} 41official_attr = ["version", "name", "device", "refdes", "footprint", "numslots", 42 "slot", "slotdef","description", "comment", "author", 43 "documentation","value","dist-license", "use-license"] 44single_attr_warning = ["device", "footprint", "author", "documentation", 45 "description", "numslots","dist-license", "use-license"] 46single_attr = ["slot"] 47multiple_attr = ["slotdef", "comment"] 48stylelist = ["line","dot","clk","dotclk","spacer","none"] 49poslist = ["l","r","t","b",""] 50typelist = ["in","out","io","oc","oe","pas","tp","tri","clk","pwr"] 51translate_pintype = {"i/o":"io", "i":"in", "o":"out", "p":"pas"} 52P_NR, P_SEQ, P_TYPE, P_STYLE, P_POS, P_NET, P_LABEL = 0,1,2,3,4,5,6 53re_section_header = re.compile("^\s*\[(?P<name>.+)]\s*$") 54 55################################## CLASSES ############################### 56 57class Pin: 58 '''Encapsulation for all data related to a pin.''' 59 def __init__(self, element): 60 61 element.extend(('', '', '', '', '', '', '')) 62 self.nr = element[P_NR].strip() 63 self.seq = element[P_SEQ].strip() 64 pintype = element[P_TYPE].lower().strip() 65 self.type = translate_pintype.get(pintype, pintype) 66 self.style = element[P_STYLE].lower().strip() 67 self.pos = element[P_POS].lower().strip() 68 self.net = element[P_NET].strip() 69 self.label = element[P_LABEL].strip() 70 71 def __str__(self): 72 str = "Pin object (nr:" + self.nr + " seq:" + self.seq + " type:" + self.type 73 str += " style:" + self.style + " pos:" + self.pos + " net:" + self.net 74 str += " label:" + self.label + ")" 75 return str 76 77 def __cmp__(self, other): 78 """ 79 Comparison function for the pin. 80 * The sorting rule is to place the nets first. 81 * The pin position is the second sorting key 82 * The pin label is the third sorting key 83 """ 84 if not isinstance(other, Pin): 85 return NotImplemented 86 ret = cmp(self.net, other.net) 87 if ret != 0: 88 return ret 89 ret = cmp(other.pos, self.pos) 90 if ret != 0: 91 return ret 92 return cmp(splitspecial(parselabel(self.label)), 93 splitspecial(parselabel(other.label))) 94 95 96 def check(self): 97 if self.style=="spacer": 98 if self.pos == "": 99 print "Error: there must be a position with a spacer.\n" 100 sys.exit() 101 if self.pos not in poslist: 102 print "Error: position is not allowed: \n", self 103 sys.exit() 104 return 105 if self.style != "none": 106 if self.seq.isdigit(): 107 string.atoi(self.seq) 108 else: 109 print "pinseq needs to be a number: \n", self 110 sys.exit() 111 if self.type not in typelist: 112 print "Pintype not allowed: \n", self 113 sys.exit() 114 if self.style not in stylelist: 115 print "Style is not allowed: \n", self 116 sys.exit() 117 if self.pos not in poslist: 118 print "Position is not allowed: \n", self 119 sys.exit() 120 if self.pos == "" and self.net == "": 121 print "There must be either position or a netlabel: \n", self 122 sys.exit() 123 124 125################################# FUNCTIONS ############################## 126 127def usage(): 128 '''Print a usage message.''' 129 print "tragesym version " + VERSION 130 print "(C) 2001,2002,2003,2004,2006,2007 by Werner Hoch <werner.ho@gmx.de>" 131 print "Usage is: ", sys.argv[0] ,"<infile> <outfile>" 132 133 134def parselabel(str): 135 '''returns a stripped label without overbar markers "\_"''' 136 slash, neg= 0, 0 137 textout="" 138 for letter in str: 139 if letter == '\\' and slash == 0: 140 slash=1 141 elif slash == 1 and letter == '_': 142 if neg == 0: 143 neg = 1 144 else: 145 neg = 0 146 slash = 0 147 else: 148 textout=textout+letter 149 slash = 0 150 151 if slash == 1 or neg == 1: 152 print '''unbalanced overbars or escapesequence: ''', str 153 print '''the overbar starts and ends with "\_" example: \"\_enable\_''' 154 print '''to write a "\" use "\\"''' 155 sys.exit() 156 return textout 157 158## round *unsigned* integer x to closest r 159def round_closest(x,r): 160 return x-(x+r/2)%r+r/2 161 162## returns the words in reverse order 163def swapwords(str): 164 list=string.split(str," ") 165 back=list[0] 166 for i in list[1:]: 167 back=i+" "+back 168 return back 169 170## split a string at the first tab or equal char 171def split_tab_equal(str,n=1): 172 list_tab=string.split(str,'\t',n) 173 list_equal=string.split(str,'=',n) 174 if len(list_tab[0]) < len(list_equal[0]): 175 return list_tab 176 else: 177 return list_equal 178 179## returns 2 dicts: (options, attr) and 2 arrays: (devices, pins) 180def readsrc(filename): 181 geda_attr={} 182 options={} 183 pins=[] 184 f = open(filename,"r") 185 content= f.readlines() 186 section="" 187 linenr=0 188 for lineraw in content: 189 line = lineraw.rstrip() 190 linenr=linenr+1 191 if len(line) == 0: 192 continue 193 match = re_section_header.match(line) 194 if match: # find a section 195 section=match.group('name') 196 continue 197 elif section=="" or line[0]=="#" \ 198 or len(string.strip(line)) == 0: # comment, empty line or no section 199 continue 200 if section=="options": 201 element=split_tab_equal(line,1) 202 if len(element) > 1: 203 options[string.strip(element[0])]=string.strip(element[1]) 204 elif section=="geda_attr": 205 element=split_tab_equal(line,1) 206 if len(element) < 2 or len(element[1].strip()) == 0: 207 print 'Warning: Empty attribute "%s" in the geda_attr section' % element[0] 208 print ' The incomplete attribute will be dropped' 209 else: 210 nr=1 211 while geda_attr.has_key((element[0],nr)): 212 nr=nr+1 213 geda_attr[(string.strip(element[0]),nr)]=string.strip(element[1]) 214 elif section=="pins": 215 element=string.split(line,"\t") 216 if len(element) > 2: 217 pins.append(Pin(element)) 218 else: 219 print linenr, ": illegal section name: ", section 220 sys.exit() 221 return options, geda_attr, pins 222 223 224def splitspecial(str): 225 """ 226 makes a list out of a string: 227 "3abc345x?" --> ["",3,"abc",345,"x?"] 228 """ 229 isletter=1 230 list=[] 231 current = "" 232 for letter in str: 233 if letter not in string.digits: 234 if isletter == 1: 235 current += letter 236 else: 237 list.append(int(current)) 238 current = letter 239 isletter=1 240 else: 241 if isletter == 0: 242 current += letter 243 else: 244 list.append(current) 245 current = letter 246 isletter=0 247 if isletter == 0: 248 list.append(int(current)) 249 else: 250 list.append(current) 251 return list 252 253def writesym(filename,options,attr,pins): 254 o_symwidth=string.atoi(options["sym_width"]) 255 o_hdist=string.atoi(options["pinwidthhorizontal"]) 256 257 # If pinwidthvertikal was defined, use it, else use pinwidthvertical 258 # This keeps compatibility with older versions, while fixing the spell 259 # bug 260 if options["pinwidthvertikal"] != preset_options["pinwidthvertikal"]: 261 o_vdist=string.atoi(options["pinwidthvertikal"]) 262 else: 263 o_vdist=string.atoi(options["pinwidthvertical"]) 264 265 o_wordswap=options["wordswap"] 266 o_rotate=options["rotate_labels"] 267 o_sort=options["sort_labels"] 268 269 pinlength = 300 270 271### Count the number of pins in each side 272 273 numpleft=0 274 numpright=0 275 numpbottom=0 276 numptop = 0 277 for pin in pins: 278 if pin.pos == "l": # left pin 279 numpleft=numpleft+1 280 elif pin.pos == "r": #right pin 281 numpright=numpright+1 282 elif pin.pos == "b": #right pin 283 numpbottom=numpbottom+1 284 elif pin.pos == "t": #right pin 285 numptop=numptop+1 286 287 # Calculate the position of the pins in the left and right side. 288 plefty, prighty = 0, 0 289 if numpleft > numpright: 290 plefty=plefty+(numpleft-1)*o_vdist 291 prighty = plefty 292 else : 293 prighty=prighty+(numpright-1)*o_vdist 294 plefty = prighty 295 296 # Calculate the bottom left of the box 297 bottomleftx, bottomlefty = pinlength + 100, 100 298 if numpbottom > 0: 299 bottomlefty += pinlength 300 301 # Calculate the minimum symwidth and increase it if necessary 302 calculated_top_symwidth=(numptop-1)*o_hdist+2*o_hdist 303 calculated_bottom_symwidth=(numpbottom-1)*o_hdist+2*o_hdist 304 305 calculated_symwidth = max(calculated_bottom_symwidth, 306 calculated_top_symwidth) 307 308 if (numptop + numpbottom > 0): 309 print "Note: use sym_width to adjust symbol width if texts overlap." 310 311 if o_symwidth == 0: 312 o_symwidth = calculated_symwidth 313 314 # Calculate the symbol's high 315 if numpleft < numpright: 316 high=(numpright+1)*o_vdist 317 else: 318 high=(numpleft+1)*o_vdist 319 topy = bottomlefty + high 320 321 # Calculate the position of several items. 322 prightx, prighty= bottomleftx + pinlength + o_symwidth, prighty + bottomlefty + o_vdist 323 pleftx, plefty= bottomleftx - pinlength, plefty + bottomlefty + o_vdist 324 ptopx, ptopy= bottomleftx + o_hdist, bottomlefty + high + pinlength 325 pbottomx, pbottomy = bottomleftx + o_hdist, bottomlefty - pinlength 326 327 # Lets add some pad if sym_width was defined 328 ptopx = ptopx + (o_symwidth - calculated_top_symwidth) / 2 329 pbottomx = pbottomx + (o_symwidth - calculated_bottom_symwidth) / 2 330 331 ptopx = round_closest(ptopx, 100) 332 pbottomx = round_closest(pbottomx, 100) 333 334 f = open(filename, "w") 335 336### Draw the symbol version 337 if attr.has_key(("version",1)): 338 value=attr[("version",1)] 339 if re.match("[0-9]{8}$", value): 340 f.write("v " + value + " 1\n") 341 elif re.match("[0-9]{8} 1$", value): 342 f.write("v " + value + "\n") 343 else: 344 print "error: version string format invalid: [%s]" % value 345 sys.exit() 346 else: 347 print "error: version attribut missing" 348 sys.exit() 349 350 if o_sort == "yes": 351 pins.sort() 352 353 for pin in pins: 354 if pin.style == "none": # 355 continue 356 if pin.style=="spacer": 357 if o_sort == "yes": 358 print "Warning: spacers are not supported when sorting labels" 359 continue 360 elif pin.pos == "l": #left pin 361 plefty=plefty - o_vdist #where to draw the _next_ pin 362 elif pin.pos == "r": #right pin 363 prighty=prighty - o_vdist 364 elif pin.pos == "b": # bottom pin 365 pbottomx=pbottomx + o_hdist 366 elif pin.pos == "t": # top pin 367 ptopx=ptopx + o_hdist 368 continue 369 370### decide which pindirection to use 371 ## TODO: put all constants into a dictionary 372 if pin.pos == "l": #left pin 373 basex, basey= pleftx, plefty #where to draw this pin 374 xf, yf= 1, 0 # orientation factors 375 pint=(200,50,6,0) # dx, dy, alignment, angle 376 pinl=(350,0,0,0) # """" 377 pina=(350,0,2,0) # """" 378 pinq=(200,-50,8,0) # """" 379 swap=0 # swap words in label ? 380 plefty=plefty - o_vdist #where to draw the _next_ pin 381 elif pin.pos == "r": #right pin 382 basex, basey = prightx, prighty 383 xf, yf= -1, 0 384 pint=(-200,50,0,0) 385 pinl=(-350,0,6,0) 386 pina=(-350,0,8,0) 387 pinq=(-200,-50,2,0) 388 swap=1 389 prighty=prighty - o_vdist 390 elif pin.pos == "b": # bottom pin 391 basex, basey=pbottomx, pbottomy 392 xf, yf= 0, 1 393 if o_rotate == "yes": # bottom pin with 90� text 394 pint=(-50,200,6,90) 395 pinl=(0,350,0,90) 396 pina=(0,350,2,90) 397 pinq=(50,200,8,90) 398 else: 399 pint=(50,200,2,0) 400 pinl=(0,350,3,0) 401 pina=(0,500,3,0) 402 pinq=(-50,200,8,0) 403 swap=0 404 pbottomx=pbottomx + o_hdist 405 elif pin.pos == "t": # top pin 406 basex, basey=ptopx, ptopy 407 xf, yf= 0, -1 408 if o_rotate == "yes": # with 90� text 409 pint=(-50,-200,0,90) 410 pinl=(0,-350,6,90) 411 pina=(0,-350,8,90) 412 pinq=(50,-200,2,90) 413 swap=1 414 else: 415 pint=(50,-200,0,0) 416 pinl=(0,-350,5,0) 417 pina=(0,-500,5,0) 418 pinq=(-50,-200,6,0) 419 swap=0 420 ptopx=ptopx + o_hdist 421### draw the pin 422 if (pin.style=="dot" or #short pin and dot? 423 pin.style=="dotclk"): 424 x=basex + xf*200 425 y=basey + yf*200 426 else: 427 x=basex + xf*300 428 y=basey + yf*300 429 f.write("P %i"%basex+" %i"%basey+" %i"%x + " %i"%y+ " 1 0 0\n") 430 f.write("{\n") 431### draw pinnumber 432 pintx, pinty, pinta, pintr=pint 433 x=basex+pintx 434 y=basey+pinty 435 f.write("T %i"%x+" %i"%y+" 5 8 1 1 %i"%pintr+" %i 1\n"%pinta) 436 f.write("pinnumber="+pin.nr+"\n") 437### draw pinseq 438 pintx, pinty, pinta, pintr=pinq 439 x=basex+pintx 440 y=basey+pinty 441 f.write("T %i"%x+" %i"%y+" 5 8 0 1 %i"%pintr+" %i 1\n"%pinta) 442 f.write("pinseq="+pin.seq+"\n") 443### draw pinlabel and pintype 444 pinlx, pinly, pinla, pinlr=pinl 445 pinax, pinay, pinaa, pinar=pina 446 if (pin.style=="clk" or #move label if clocksign 447 pin.style=="dotclk"): 448 pinlx=pinlx + xf*75 449 pinly=pinly + yf*75 450 pinax=pinax + xf*75 451 pinay=pinay + yf*75 452 pinlx=pinlx + basex 453 pinly=pinly + basey 454 pinax=pinax + basex 455 pinay=pinay + basey 456 if o_wordswap=="yes" and swap==1: 457 label=swapwords(pin.label) 458 else: 459 label=pin.label 460 f.write("T %i"%pinlx+" %i"%pinly+" 9 8 1 1 %i"%pinlr+" %i 1\n"%pinla) 461 f.write("pinlabel="+label+"\n") 462 f.write("T %i"%pinax+" %i"%pinay+" 5 8 0 1 %i"%pinar+" %i 1\n"%pinaa) 463 f.write("pintype="+pin.type+"\n") 464 f.write("}\n") 465### draw the negation bubble 466 if (pin.style=="dot" or pin.style=="dotclk"): 467 x=basex + xf*250 468 y=basey + yf*250 469 f.write("V %i"%x+" %i"%y +" 50 6 0 0 0 -1 -1 0 -1 -1 -1 -1 -1\n") 470### draw the clocksign 471 if (pin.style=="clk" or 472 pin.style=="dotclk"): 473 x1=basex+ xf*400 474 y1=basey+ yf*400 475 x2=x1- xf*100 +yf*75 476 y2=y1- yf*100 +xf*75 477 x3=x1- xf*100 -yf*75 478 y3=y1- yf*100 -xf*75 479 f.write("L %i"%x1+" %i"%y1+" %i"%x2+" %i"%y2 + " 3 0 0 0 -1 -1\n") 480 f.write("L %i"%x1+" %i"%y1+" %i"%x3+" %i"%y3 + " 3 0 0 0 -1 -1\n") 481### draw a box 482 f.write("B %i"%bottomleftx+" %i"%bottomlefty+" %i"%o_symwidth+" %i"%high+ 483 " 3 0 0 0 -1 -1 0 -1 -1 -1 -1 -1\n") 484 485### draw the attributes 486 urefx, urefy = bottomleftx+o_symwidth, bottomlefty + high + 100 487 488 # Center name if we have top pins 489 if numptop > 0: 490 namex, namey = (bottomleftx + o_symwidth) / 2, (bottomlefty + high) / 2 + 100 491 else: 492 namex, namey = bottomleftx, bottomlefty+high+100 493 494 textx = namex 495 texty = namey + 200 496 if numptop > 0: 497 texty += 100 498 499 ## special attribute format 500 if attr.has_key(("refdes",1)): 501 f.write("T %i"% urefx +" %i"% urefy +" 8 10 1 1 0 6 1\n") 502 f.write("refdes=" + attr[("refdes",1)] + "\n") 503 else: 504 print "Warning: refdes attribut missing" 505 506 if attr.has_key(("name",1)): 507 f.write("T %i" %namex + " %i"% namey + " 9 10 1 0 0 0 1\n") 508 f.write(attr[("name",1)] + "\n") 509 else: 510 print "Warning: name attribut missing" 511 512 ## attributes with same format and warnings 513 for a in single_attr_warning: 514 if attr.has_key((a,1)): 515 f.write("T %i" %textx + " %i"% texty + " 5 10 0 0 0 0 1\n") 516 f.write(a + "=" + attr[(a,1)] + "\n") 517 texty=texty+200 518 else: 519 print "Warning: " + a + " attribut missing" 520 521 ## attributes without warning 522 for a in single_attr: 523 if attr.has_key((a,1)): 524 f.write("T %i" %textx + " %i"% texty + " 5 10 0 0 0 0 1\n") 525 f.write(a + "=" + attr[(a,1)] + "\n") 526 texty=texty+200 527 528 ## attributes with more than one equal name 529 for a in multiple_attr: 530 i = 1 531 while attr.has_key((a,i)): 532 f.write("T %i" %textx + " %i"% texty + " 5 10 0 0 0 0 1\n") 533 f.write(a + "=" + attr[(a,i)] + "\n") 534 texty=texty+200 535 i = i + 1 536 537 ## unknown attributes 538 for (name, number),value in attr.items(): 539 if name not in official_attr: 540 f.write("T %i" %textx + " %i"% texty + " 5 10 0 0 0 0 1\n") 541 f.write(name + "=" + value + "\n") 542 texty=texty+200 543 print 'Warning: The attribute "%s=%s" is not official' %(name, value) 544 545 nets={} 546 for pin in pins: 547 if pin.style == "none": 548 if not nets.has_key(pin.net): 549 nets[pin.net] = pin.nr 550 else: 551 nets[pin.net] = nets[pin.net] + ","+ pin.nr 552 for key,value in nets.items(): 553 f.write("T %i" %textx + " %i"% texty + " 5 10 0 0 0 0 1\n") 554 f.write("net=" + key + ":" + value + "\n") 555 texty=texty+200 556 557 return 0 558 559def mergeoptions(source_opt,pre_opt): 560 ret=pre_opt 561 for item in source_opt.keys(): 562 if ret.has_key(item): 563 ret[item]=source_opt[item] 564 else: 565 print "This option is not allowed:", item 566 sys.exit() 567 return ret 568 569def generate_pinseq(pins): 570 seq=1 571 for nr in xrange(len(pins)): 572 if pins[nr].style not in ["none","spacer"]: 573 pins[nr].seq = "%i"%seq 574 seq = seq + 1 575 return pins 576 577###################### MAIN ################################################# 578 579## parse command line options 580try: 581 opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) 582except: 583 usage() 584 sys.exit() 585 586## handle command line options 587for o, a in opts: 588 if o in ("-h", "--help"): 589 usage() 590 sys.exit() 591 592## get files 593if len(args) != 2: 594 usage() 595 sys.exit() 596 597file_in=args[0] 598file_out=args[1] 599if not os.path.exists(file_in): 600 print "Input file " + file_in + " not found." 601 sys.exit() 602 603## read sourcefile 604opts,attr,pins=readsrc(file_in) 605 606options=mergeoptions(opts,preset_options) 607 608if options["generate_pinseq"] == "yes": 609 pins=generate_pinseq(pins) 610 611for pin in pins: 612 pin.check() 613 614writesym(file_out,options,attr,pins) 615 616