hardcoded python path in garchive and tragesym
[geda-gaf.git] / utils / scripts / tragesym
blob9a95d44ea63aab02681b93adcef9a0d1c6e058b1
1 #!/usr/bin/env python2.7
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 ############################################################################
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
27 import getopt, os.path, re, string, sys
29 ##################### GLOBALS ############################################
30 VERSION="0.0.15"
32 CHARHIGH=26
33 preset_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"}
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 ###############################
57 class 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()
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
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)))
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 ##############################
127 def 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>"
134 def 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
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    
163 def swapwords(str): 
164     list=string.split(str," ")
165     back=list[0]
166     for i in list[1:]:
167         back=i+" "+back
168     return back
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]):
175         return list_tab
176     else:
177         return list_equal
179 ## returns 2 dicts: (options, attr) and 2 arrays: (devices, pins)
180 def 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
224 def 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
253 def 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"])
265     o_wordswap=options["wordswap"]
266     o_rotate=options["rotate_labels"]
267     o_sort=options["sort_labels"]
269     pinlength = 300
271 ### Count the number of pins in each side
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
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
296     # Calculate the bottom left of the box
297     bottomleftx, bottomlefty = pinlength + 100, 100
298     if numpbottom > 0:
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
304     
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."
311     if o_symwidth == 0:
312         o_symwidth = calculated_symwidth
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
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)
333     
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")
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()
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")
485 ### draw the attributes
486     urefx, urefy = bottomleftx+o_symwidth, bottomlefty + high + 100
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
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"
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"
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"
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
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
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)
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
557     return 0
559 def 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
569 def 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
577 ###################### MAIN #################################################
579 ## parse command line options
580 try:
581     opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
582 except:
583     usage()
584     sys.exit()
586 ## handle command line options
587 for o, a in opts:
588     if o in ("-h", "--help"):
589         usage()
590         sys.exit()
592 ## get files
593 if len(args) != 2:
594     usage()
595     sys.exit()
597 file_in=args[0]
598 file_out=args[1]
599 if not os.path.exists(file_in):
600     print "Input file " + file_in + " not found."
601     sys.exit()
603 ## read sourcefile
604 opts,attr,pins=readsrc(file_in)
606 options=mergeoptions(opts,preset_options)
608 if options["generate_pinseq"] == "yes":
609     pins=generate_pinseq(pins)
611 for pin in pins:
612     pin.check()
614 writesym(file_out,options,attr,pins)