scheme-api: Remove redundant checks in edascm_to_object_glist().
[geda-gaf/peter-b.git] / utils / scripts / tragesym
blob7175ffa213caf632f8c03c732d05ca5886ee9c95
1 #!/usr/bin/python
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):
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()
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
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):
226 makes a list out of a string:
227 "3abc345x?" --> ["",3,"abc",345,"x?"]
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"])
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
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)
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()
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
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
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)