1 #!/usr/bin/env python2.7
3 ############################################################################
4 # tragesym - create gEDA symbols out of structured textfiles
6 # copyright : (C) 2001,2002,2003,2004,2006,2007, 2008 by Werner Hoch
7 # email : werner.ho@gmx.de
8 ############################################################################
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. #
15 ############################################################################
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
27 import getopt, os.path, re, string, sys
29 ##################### GLOBALS ############################################
33 preset_options = {"wordswap":"yes",
36 "generate_pinseq":"yes",
38 "pinwidthvertikal":"400",
39 "pinwidthvertical":"400",
40 "pinwidthhorizontal":"400"}
41 official_attr = ["version", "name", "device", "refdes", "footprint", "numslots",
42 "slot", "slotdef","description", "comment", "author",
43 "documentation","value","dist-license", "use-license"]
44 single_attr_warning = ["device", "footprint", "author", "documentation",
45 "description", "numslots","dist-license", "use-license"]
46 single_attr = ["slot"]
47 multiple_attr = ["slotdef", "comment"]
48 stylelist = ["line","dot","clk","dotclk","spacer","none"]
49 poslist = ["l","r","t","b",""]
50 typelist = ["in","out","io","oc","oe","pas","tp","tri","clk","pwr"]
51 translate_pintype = {"i/o":"io", "i":"in", "o":"out", "p":"pas"}
52 P_NR, P_SEQ, P_TYPE, P_STYLE, P_POS, P_NET, P_LABEL = 0,1,2,3,4,5,6
53 re_section_header = re.compile("^\s*\[(?P<name>.+)]\s*$")
55 ################################## CLASSES ###############################
58 '''Encapsulation for all data related to a pin.'''
59 def __init__(self, element):
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()
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 + ")"
77 def __cmp__(self, other):
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
84 if not isinstance(other, Pin):
86 ret = cmp(self.net, other.net)
89 ret = cmp(other.pos, self.pos)
92 return cmp(splitspecial(parselabel(self.label)),
93 splitspecial(parselabel(other.label)))
97 if self.style=="spacer":
99 print "Error: there must be a position with a spacer.\n"
101 if self.pos not in poslist:
102 print "Error: position is not allowed: \n", self
105 if self.style != "none":
106 if self.seq.isdigit():
107 string.atoi(self.seq)
109 print "pinseq needs to be a number: \n", self
111 if self.type not in typelist:
112 print "Pintype not allowed: \n", self
114 if self.style not in stylelist:
115 print "Style is not allowed: \n", self
117 if self.pos not in poslist:
118 print "Position is not allowed: \n", self
120 if self.pos == "" and self.net == "":
121 print "There must be either position or a netlabel: \n", self
125 ################################# FUNCTIONS ##############################
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>"
135 '''returns a stripped label without overbar markers "\_"'''
139 if letter == '\\' and slash == 0:
141 elif slash == 1 and letter == '_':
148 textout=textout+letter
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 "\\"'''
158 ## round *unsigned* integer x to closest r
159 def round_closest(x,r):
160 return x-(x+r/2)%r+r/2
162 ## returns the words in reverse order
164 list=string.split(str," ")
170 ## split a string at the first tab or equal char
171 def 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]):
179 ## returns 2 dicts: (options, attr) and 2 arrays: (devices, pins)
180 def readsrc(filename):
184 f = open(filename,"r")
185 content= f.readlines()
188 for lineraw in content:
189 line = lineraw.rstrip()
193 match = re_section_header.match(line)
194 if match: # find a section
195 section=match.group('name')
197 elif section=="" or line[0]=="#" \
198 or len(string.strip(line)) == 0: # comment, empty line or no section
200 if section=="options":
201 element=split_tab_equal(line,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'
211 while geda_attr.has_key((element[0],nr)):
213 geda_attr[(string.strip(element[0]),nr)]=string.strip(element[1])
214 elif section=="pins":
215 element=string.split(line,"\t")
217 pins.append(Pin(element))
219 print linenr, ": illegal section name: ", section
221 return options, geda_attr, pins
224 def splitspecial(str):
226 makes a list out of a string:
227 "3abc345x?" --> ["",3,"abc",345,"x?"]
233 if letter not in string.digits:
237 list.append(int(current))
248 list.append(int(current))
253 def writesym(filename,options,attr,pins):
254 o_symwidth=string.atoi(options["sym_width"])
255 o_hdist=string.atoi(options["pinwidthhorizontal"])
257 # If pinwidthvertikal was defined, use it, else use pinwidthvertical
258 # This keeps compatibility with older versions, while fixing the spell
260 if options["pinwidthvertikal"] != preset_options["pinwidthvertikal"]:
261 o_vdist=string.atoi(options["pinwidthvertikal"])
263 o_vdist=string.atoi(options["pinwidthvertical"])
265 o_wordswap=options["wordswap"]
266 o_rotate=options["rotate_labels"]
267 o_sort=options["sort_labels"]
271 ### Count the number of pins in each side
278 if pin.pos == "l": # left pin
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
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
293 prighty=prighty+(numpright-1)*o_vdist
296 # Calculate the bottom left of the box
297 bottomleftx, bottomlefty = pinlength + 100, 100
299 bottomlefty += pinlength
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
305 calculated_symwidth = max(calculated_bottom_symwidth,
306 calculated_top_symwidth)
308 if (numptop + numpbottom > 0):
309 print "Note: use sym_width to adjust symbol width if texts overlap."
312 o_symwidth = calculated_symwidth
314 # Calculate the symbol's high
315 if numpleft < numpright:
316 high=(numpright+1)*o_vdist
318 high=(numpleft+1)*o_vdist
319 topy = bottomlefty + high
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
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
331 ptopx = round_closest(ptopx, 100)
332 pbottomx = round_closest(pbottomx, 100)
334 f = open(filename, "w")
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")
344 print "error: version string format invalid: [%s]" % value
347 print "error: version attribut missing"
354 if pin.style == "none": #
356 if pin.style=="spacer":
358 print "Warning: spacers are not supported when sorting labels"
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
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
389 prighty=prighty - o_vdist
390 elif pin.pos == "b": # bottom pin
391 basex, basey=pbottomx, pbottomy
393 if o_rotate == "yes": # bottom pin with 90° text
404 pbottomx=pbottomx + o_hdist
405 elif pin.pos == "t": # top pin
406 basex, basey=ptopx, ptopy
408 if o_rotate == "yes": # with 90° text
420 ptopx=ptopx + o_hdist
422 if (pin.style=="dot" or #short pin and dot?
423 pin.style=="dotclk"):
429 f.write("P %i"%basex+" %i"%basey+" %i"%x + " %i"%y+ " 1 0 0\n")
432 pintx, pinty, pinta, pintr=pint
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")
438 pintx, pinty, pinta, pintr=pinq
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"):
456 if o_wordswap=="yes" and swap==1:
457 label=swapwords(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")
465 ### draw the negation bubble
466 if (pin.style=="dot" or pin.style=="dotclk"):
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"):
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")
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")
485 ### draw the attributes
486 urefx, urefy = bottomleftx+o_symwidth, bottomlefty + high + 100
488 # Center name if we have top pins
490 namex, namey = (bottomleftx + o_symwidth) / 2, (bottomlefty + high) / 2 + 100
492 namex, namey = bottomleftx, bottomlefty+high+100
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")
504 print "Warning: refdes attribut missing"
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")
510 print "Warning: name attribut missing"
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")
519 print "Warning: " + a + " attribut missing"
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")
528 ## attributes with more than one equal name
529 for a in multiple_attr:
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")
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")
543 print 'Warning: The attribute "%s=%s" is not official' %(name, value)
547 if pin.style == "none":
548 if not nets.has_key(pin.net):
549 nets[pin.net] = pin.nr
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")
559 def mergeoptions(source_opt,pre_opt):
561 for item in source_opt.keys():
562 if ret.has_key(item):
563 ret[item]=source_opt[item]
565 print "This option is not allowed:", item
569 def generate_pinseq(pins):
571 for nr in xrange(len(pins)):
572 if pins[nr].style not in ["none","spacer"]:
573 pins[nr].seq = "%i"%seq
577 ###################### MAIN #################################################
579 ## parse command line options
581 opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
586 ## handle command line options
588 if o in ("-h", "--help"):
599 if not os.path.exists(file_in):
600 print "Input file " + file_in + " not found."
604 opts,attr,pins=readsrc(file_in)
606 options=mergeoptions(opts,preset_options)
608 if options["generate_pinseq"] == "yes":
609 pins=generate_pinseq(pins)
614 writesym(file_out,options,attr,pins)