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