Scripts:
[blender.git] / release / scripts / mod_svg2obj.py
1 """
2 SVG 2 OBJ translater, 0.2.6
3 (c) jm soler juillet/novembre 2004, released under Blender Artistic Licence 
4     for the Blender 2.34/35 Python Scripts Bundle.
5 #---------------------------------------------------------------------------
6 # Page officielle :
7 #   http://jmsoler.free.fr/didacticiel/blender/tutor/cpl_import_svg.htm
8 # Communiquer les problemes et erreurs sur:
9 #   http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender
10 #---------------------------------------------------------------------------
11
12 -- Concept : translate SVG file in GEO .obj file and try to load it. 
13 -- Curiousity : the original matrix must be :
14
15                          0.0 0.0 1.0 0.0
16                          0.0 1.0 0.0 0.0
17                          0.0 0.0 1.0 0.0
18                          0.0 0.0 0.0 1.0 
19
20                   and not:
21                          1.0 0.0 0.0 0.0
22                          0.0 1.0 0.0 0.0
23                          0.0 0.0 1.0 0.0
24                          0.0 0.0 0.0 1.0 
25
26 -- Options :
27     SHARP_IMPORT = 0 
28             choise between "As is", "Devide by height" and "Devide by width"
29     SHARP_IMPORT = 1
30             no choise
31
32 -- Possible bug : sometime, the new curves object's RotY value 
33                   jumps to -90.0 degrees without any reason.
34
35 Yet done: 
36    M : absolute move to 
37    Z : close path
38    L : absolute line to  
39    C : absolute curve to
40    S : absolute curve to with only one handle
41    l : relative line to     2004/08/03
42    c : relative curve to    2004/08/03
43    s : relative curve to with only one handle  
44
45 To do:  A,S,V,H,Q,T, 
46         a,s, m, v, h, q,t
47
48 Changelog:
49       0.1.1 : - control file without extension
50       0.2.0 : - improved reading of several data of the same type 
51                 following the same command (for gimp import)
52       0.2.1 : - better choice for viewboxing ( takes the viewbox if found, 
53                 instead of x,y,width and height              
54       0.2.2 : - read compact path data from Illustrator 10             
55       0.2.3 : - read a few new relative displacements
56       0.2.4 : - better hash for command with followed by a lone data 
57                 (h,v) or uncommun number (a) 
58       0.2.5 : - correction for gimp import 
59       0.2.6 : - correction for illustrator 10 SVG  
60                 
61 ==================================================================================   
62 =================================================================================="""
63
64 SHARP_IMPORT=0
65 SCALE=1
66 scale=1
67 DEBUG =0 #print
68 DEVELOPPEMENT=0
69     
70 import sys
71 #oldpath=sys.path
72 import Blender
73 BLversion=Blender.Get('version')
74
75 try:
76     import nt
77     os=nt
78     os.sep='\\'
79
80 except:    
81     import posix
82     os=posix
83     os.sep='/'
84     
85 def isdir(path):
86     try:
87         st = os.stat(path)
88         return 1 
89     except:
90         return 0
91     
92 def split(pathname):
93          if pathname.find(os.sep)!=-1:
94              k0=pathname.split(os.sep)
95          else:
96             if os.sep=='/':
97                 k0=pathname.split('\\')
98             else:
99                 k0=pathname.split('/') 
100
101          directory=pathname.replace(k0[len(k0)-1],'')
102          Name=k0[len(k0)-1]
103          return directory, Name
104         
105 def join(l0,l1):        
106      return  l0+os.sep+l1
107     
108 os.isdir=isdir
109 os.split=split
110 os.join=join
111
112 def filtreFICHIER(nom):
113      f=open(nom,'rU')
114      t=f.read()
115      f.close()
116      
117      t=t.replace('\r','')
118      t=t.replace('\n','')
119      
120      if t.upper().find('<SVG')==-1 :
121          name = "OK?%t| Not a valid file or an empty file ... "  # if no %xN int is set, indices start from 1
122          result = Blender.Draw.PupMenu(name)
123          return "false"
124      elif  t.upper().find('<PATH')==-1:
125          name = "OK?%t| Sorry, no Path in this file ... "  # if no %xN int is set, indices start from 1
126          result = Blender.Draw.PupMenu(name)
127          return "false"
128      else:
129           return t
130
131 #===============================
132 # Data
133 #===============================
134 #===============================
135 # Blender Curve Data
136 #===============================
137 objBEZIER=0
138 objSURFACE=5
139 typBEZIER3D=1  #3D
140 typBEZIER2D=9  #2D
141
142 class Bez:
143       def __init__(self):
144            self.co=[]
145            self.ha=[0,0]
146            
147 class ITEM:
148       def __init__(self):
149                self.type        =  typBEZIER3D,        
150                self.pntsUV      =  [0,0]              
151                self.resolUV     =  [32,0]            
152                self.orderUV     =  [0,0]             
153                self.flagUV      =  [0,0]              
154                self.Origine     =  [0.0,0.0]
155                self.beziers_knot = []
156
157 class COURBE:
158       def __init__(self):
159               self.magic_number='3DG3'              
160               self.type            =  objBEZIER        
161               self.number_of_items =  0              
162               self.ext1_ext2       =  [0,0]             
163               self.matrix          =  """0.0 0.0 1.0 0.0
164 0.0 1.0 0.0 0.0
165 0.0 0.0 1.0 0.0
166 0.0 0.0 0.0 1.0 """ 
167               self.ITEM = {}
168
169
170 courbes=COURBE()
171 PATTERN={}
172 BOUNDINGBOX={'rec':[],'coef':1.0}
173 npat=0
174 #=====================================================================
175 #======== name of the curve in the curves dictionnary ===============
176 #=====================================================================
177 n0=0
178
179 #=====================================================================
180 #====================== current Point ================================
181 #=====================================================================
182 CP=[0.0,0.0] #currentPoint
183
184 #=====================================================================
185 #===== to compare last position to the original move to displacement =
186 #=====  needed for cyclic definition inAI, EPS forma  ================
187 #=====================================================================
188 def test_egalitedespositions(f1,f2):
189     if f1[0]==f2[0] and f1[1]==f2[1]:
190        return Blender.TRUE
191     else:
192        return Blender.FALSE
193
194
195 def Open_GEOfile(dir,nom):
196     global SCALE,BOUNDINGBOX, scale
197     if BLversion>=233:
198        Blender.Load(dir+nom+'OOO.obj', 1)
199        BO=Blender.Object.Get()
200
201        BO[-1].RotY=3.1416
202        BO[-1].RotZ=3.1416
203        BO[-1].RotX=3.1416/2.0
204        
205        if scale==1:
206           BO[-1].LocY+=BOUNDINGBOX['rec'][3]
207        else:
208          BO[-1].LocY+=BOUNDINGBOX['rec'][3]/SCALE
209  
210        BO[-1].makeDisplayList() 
211        Blender.Window.RedrawAll()
212     else:
213        print "Not yet implemented"
214
215 def create_GEOtext(courbes):
216     global SCALE, B, BOUNDINGBOX,scale
217     r=BOUNDINGBOX['rec']
218
219     if scale==1:
220        SCALE=1.0
221     elif scale==2:
222        SCALE=r[2]-r[0]
223     elif scale==3:
224        SCALE=r[3]-r[1]
225  
226     t=[]
227     t.append(courbes.magic_number+'\n')
228     t.append(str(courbes.type)+'\n')
229     t.append(str(courbes.number_of_items)+'\n')
230     t.append(str(courbes.ext1_ext2[0])+' '+str(courbes.ext1_ext2[1])+'\n')
231     t.append(courbes.matrix+'\n')
232     
233     for k in courbes.ITEM.keys():
234         t.append("%s\n"%courbes.ITEM[k].type)
235         t.append("%s %s \n"%(courbes.ITEM[k].pntsUV[0],courbes.ITEM[k].pntsUV[1]))
236         t.append("%s %s \n"%(courbes.ITEM[k].resolUV[0],courbes.ITEM[k].resolUV[1]))
237         t.append("%s %s \n"%(courbes.ITEM[k].orderUV[0],courbes.ITEM[k].orderUV[1]))
238         t.append("%s %s \n"%(courbes.ITEM[k].flagUV[0],courbes.ITEM[k].flagUV[1]))
239
240         flag =0#courbes.ITEM[k].flagUV[0]
241
242         for k2 in range(flag,len(courbes.ITEM[k].beziers_knot)):
243            #k1 =courbes.ITEM[k].beziers_knot[k2]
244            k1=ajustement(courbes.ITEM[k].beziers_knot[k2], SCALE)
245            
246            t.append("%4f 0.0 %4f \n"%(k1[4],k1[5]))
247            t.append("%4f 0.0 %4f \n"%(k1[0],k1[1]))
248            t.append("%4f 0.0 %4f \n"%(k1[2],k1[3]))
249            t.append(str(courbes.ITEM[k].beziers_knot[k2].ha[0])+' '+str(courbes.ITEM[k].beziers_knot[k2].ha[1])+'\n')
250
251     return t
252
253 def save_GEOfile(dir,nom,t):
254      f=open(dir+nom+'OOO.obj','w')
255      f.writelines(t)
256      f.close()
257      #warning = "REMINDER : %t | Do not forget to rename your blender file NOW ! %x1"
258      #result = Blender.Draw.PupMenu(warning)
259
260
261 def filtre_DATA(c,D,n):
262     global DEBUG,TAGcourbe
263     l=[] 
264
265     if len(c[0])==1 and D[c[1]+1].find(',')!=-1:
266         for n2 in range(1,n+1): 
267            ld=D[c[1]+n2].split(',')
268            for l_ in ld: 
269                l.append(l_)
270                
271     elif len(c[0])==1 and D[c[1]+2][0] not in  TAGcourbe:
272         for n2 in range(1,n*2+1):
273            l.append(D[c[1]+n2])
274         if DEBUG==1 : print l 
275
276     return l
277
278 #=====================================================================
279 #=====      SVG format   :  DEBUT             =========================
280 #=====================================================================
281
282 def contruit_SYMETRIC(l):
283     L=[float(l[0]), float(l[1]),
284        float(l[2]),float(l[3])]
285     X=L[0]-(L[2]-L[0])
286     Y=L[1]-(L[3]-L[1])
287     l =[l[0],l[1],"%4s"%X,"%4s"%Y,l[2],l[3]]   
288     return l
289
290 def mouvement_vers(c, D, n0,CP):
291     global DEBUG,TAGcourbe
292     #print c,D[c[1]+1]
293
294     l=filtre_DATA(c,D,1)
295     #print l
296     if n0 in courbes.ITEM.keys():
297        n0+=1
298        CP=[l[0],l[1]]        
299     else:
300        CP=[l[0],l[1]] 
301
302     courbes.ITEM[n0]=ITEM() 
303     courbes.ITEM[n0].Origine=[l[0],l[1]] 
304
305     B=Bez()
306     B.co=[CP[0],CP[1],CP[0],CP[1],CP[0],CP[1]]
307     B.ha=[0,0]
308     
309     courbes.ITEM[n0].beziers_knot.append(B)
310     if DEBUG==1: print courbes.ITEM[n0], CP
311     
312
313     return  courbes,n0,CP     
314     
315 def boucle_z(c,D,n0,CP): #Z,z
316     #print c, 'close'
317     courbes.ITEM[n0].flagUV[0]=1 
318     return  courbes,n0,CP    
319    
320 def courbe_vers_s(c,D,n0,CP):  #S,s
321     l=filtre_DATA(c,D,2) 
322     if c[0]=='s':
323        l=["%4s"%(float(l[0])+float(CP[0])),
324           "%4s"%(float(l[1])+float(CP[1])),
325           "%4s"%(float(l[2])+float(CP[0])),
326           "%4s"%(float(l[3])+float(CP[1]))]
327     l=contruit_SYMETRIC(l)    
328     B=Bez()
329     B.co=[l[4],l[5],l[2],l[3],l[0],l[1]] #plus toucher au 2-3
330     B.ha=[0,0]
331
332     BP=courbes.ITEM[n0].beziers_knot[-1]
333     BP.co[2]=l[2]  #4-5 point prec
334     BP.co[3]=l[3]
335
336     courbes.ITEM[n0].beziers_knot.append(B)
337     if DEBUG==1: print B.co,BP.co
338     CP=[l[4],l[5]]    
339
340     if len(D)<c[1]+3 and D[c[1]+3] not in TAGcourbe :
341         c[1]+=2
342         courbe_vers_c(c, D, n0,CP)
343     return  courbes,n0,CP
344
345 def courbe_vers_a(c,D,n0,CP):  #A
346     #print c
347     return  courbes,n0,CP     
348
349 def courbe_vers_q(c,D,n0,CP):  #Q
350     #print c
351     return  courbes,n0,CP     
352
353 def courbe_vers_t(c,D,n0,CP):  #T
354     return  courbes,n0,CP     
355        
356 def courbe_vers_c(c, D, n0,CP): #c,C
357
358     l=filtre_DATA(c,D,3) 
359     #print l, c, CP
360
361     if c[0]=='c':
362        l=["%4s"%(float(l[0])+float(CP[0])),
363           "%4s"%(float(l[1])+float(CP[1])),
364           "%4s"%(float(l[2])+float(CP[0])),
365           "%4s"%(float(l[3])+float(CP[1])),
366           "%4s"%(float(l[4])+float(CP[0])),
367           "%4s"%(float(l[5])+float(CP[1]))]
368
369     #print l
370    
371     B=Bez()
372     B.co=[l[4],
373           l[5],
374           l[0],
375           l[1],
376           l[2],
377           l[3]] #plus toucher au 2-3
378
379     B.ha=[0,0]
380
381     BP=courbes.ITEM[n0].beziers_knot[-1]
382
383     BP.co[2]=l[0]
384     BP.co[3]=l[1]
385
386     courbes.ITEM[n0].beziers_knot.append(B)
387     if DEBUG==1: print B.co,BP.co
388
389     CP=[l[4],l[5]]
390     if DEBUG==1:
391        print 'D[c[1]]', D[c[1]], c
392        print D
393     if len(D)<c[1]+4 and D[c[1]+4] not in TAGcourbe :
394         c[1]+=3
395         courbe_vers_c(c, D, n0,CP)
396
397     return  courbes,n0,CP
398     
399     
400 def ligne_tracee_l(c, D, n0,CP): #L,l
401     #print c
402     
403     l=filtre_DATA(c,D,1)
404     if c[0]=='l':
405        l=["%4s"%(float(l[0])+float(CP[0])),
406           "%4s"%(float(l[1])+float(CP[1]))]
407
408     B=Bez()
409     B.co=[l[0],l[1],l[0],l[1],l[0],l[1]]
410     B.ha=[0,0]
411     courbes.ITEM[n0].beziers_knot.append(B)    
412
413     CP=[l[0],l[1]]
414
415     if len(D)<c[1]+2 and D[c[1]+2] not in TAGcourbe :
416         c[1]+=1
417         ligne_tracee_l(c, D, n0,CP) #L
418             
419     return  courbes,n0,CP    
420     
421     
422 def ligne_tracee_h(c,D,n0,CP): #H,h
423          
424     return  courbes,n0,CP    
425
426 def ligne_tracee_v(c,D,n0,CP): #V
427     #print c
428     #CP=[]
429     return  courbes,n0,CP    
430
431 def boucle_tracee_z(c,D,n0,CP): #Z
432     #print c
433     #CP=[]
434     return  courbes,n0,CP    
435      
436 Actions=   {     "C" : courbe_vers_c,
437                  "A" : courbe_vers_a, 
438                  "S" : courbe_vers_s,
439                  "M" : mouvement_vers,
440                  "V" : ligne_tracee_v,
441                  "L" : ligne_tracee_l,
442                  "H" : ligne_tracee_h,                
443                  "Z" : boucle_z,
444                  "Q" : courbe_vers_q,
445                  "T" : courbe_vers_t,
446
447                  "c" : courbe_vers_c,
448                  "a" : courbe_vers_a, 
449                  "s" : courbe_vers_s,
450                  "m" : mouvement_vers,
451                  "v" : ligne_tracee_v,
452                  "l" : ligne_tracee_l,
453                  "h" : ligne_tracee_h,                
454                  "z" : boucle_z,
455                  "q" : courbe_vers_q,
456                  "T" : courbe_vers_t
457 }
458      
459 TAGcourbe=Actions.keys()
460
461 def get_content(val,t0):
462     t=t0[:] 
463     if t.find(' '+val+'="')!=-1:
464        t=t[t.find(' '+val+'="')+len(' '+val+'="'):]
465        val=t[:t.find('"')]
466        t=t[t.find('"'):]
467        #----------------------------------------------------------------
468        #print t[:10], val
469        #wait=raw_input('wait:'  )
470
471        return t0,val
472     else:
473        return t0,0
474
475 def get_tag(val,t):
476
477     t=t[t.find('<'+val):]
478     val=t[:t.find('>')+1]
479     t=t[t.find('>')+1:]
480     
481     if DEBUG==3 : print t[:10], val
482
483     return t,val
484     
485 def get_val(val,t):
486     d=""
487     for l in t:
488         if l.find(val+'="')!=-1:
489             d=l[l[:-1].rfind('"')+1:-1]
490             for nn in d  :
491                 if '012345670.'.find(nn)==-1:
492                      d=d.replace(nn,"")
493             d=float(d)
494             break
495         d=0.0 
496     return d
497
498 def get_BOUNDBOX(BOUNDINGBOX,SVG,viewbox):
499     if viewbox==0:
500         h=get_val('height',SVG)
501         w=get_val('width',SVG)
502         BOUNDINGBOX['rec']=[0.0,0.0,w,h]
503         r=BOUNDINGBOX['rec']
504         BOUNDINGBOX['coef']=w/h       
505     else:
506         viewbox=viewbox.split()
507         BOUNDINGBOX['rec']=[float(viewbox[0]),float(viewbox[1]),float(viewbox[2]),float(viewbox[3])]
508         r=BOUNDINGBOX['rec']
509         BOUNDINGBOX['coef']=(r[2]-r[0])/(r[3]-r[1])       
510
511     return BOUNDINGBOX
512
513 def unpack_DATA(DATA):
514     DATA[0]=DATA[0].replace('-',',-')
515     for d in Actions.keys():
516         DATA[0]=DATA[0].replace(d,','+d+',')
517     DATA[0]=DATA[0].replace(',,',',')
518     if DATA[0][0]==',':DATA[0]=DATA[0][1:]
519     if DATA[0][-1]==',':DATA[0]=DATA[0][:-1]
520     DATA[0]=DATA[0].replace('\n','')
521     DATA[0]=DATA[0].replace('\t','')
522     DATA[0]=DATA[0].split(',')
523     D2=[]
524     D1=DATA[0]
525     
526     for cell in range(len(D1)):
527        if D1[cell] in Actions.keys():
528           D2.append(D1[cell])
529           n=1
530           if D1[cell] not in ['h','v','H','V','a','A']:
531               while cell+n+1<len(D1) and (D1[cell+n] not in  Actions.keys()):
532                  D2.append(D1[cell+n]+','+D1[cell+n+1])               
533                  n+=2
534           elif D1[cell] in ['h','v','H','V']:       
535               while cell+n+1<len(D1) and (D1[cell+n] not in  Actions.keys()):
536                  D2.append(D1[cell+n])
537                  n+=1
538           elif D1[cell] in ['a','A']:       
539                  #(rx ry rotation-axe-x drapeau-arc-large drapeau-balayage x y)
540                  #150,150 0 1,0 150,-150
541                  D2.append(D1[cell+n]+','+D1[cell+n+1])
542                  D2.append(D1[cell+n+2])
543                  D2.append(D1[cell+n+3]+','+D1[cell+n+4])
544                  D2.append(D1[cell+n+5]+','+D1[cell+n+6])
545                  n+=7
546     return D2
547
548 def format_PATH(t):
549
550     t,PATH=get_tag('path',t)
551
552     if PATH.find(' id="')!=-1:
553        PATH,ID=get_content('id',PATH)
554        #print 'ident = ', ID
555
556     if PATH.find(' STROKE="')!=-1:
557        PATH,ID=get_content('stroke',PATH)
558        #print 'path stroke = ', ID
559
560     if PATH.find(' stroke-width="')!=-1:
561        PATH,ID=get_content('stroke-width',PATH)
562        #print 'path stroke-width = ', ID
563
564     if PATH.find(' d="')!=-1:
565        PATH,D=get_content('d',PATH)
566        
567     #print "D0= :",D
568        
569     D=D.split(' ')
570     
571     try:
572       while D.index(''):
573          del D[D.index('')]
574     except:
575       pass
576     
577     #print len(D)
578     #for D0 in D:
579         #print "  ---->  D  = :", D0
580
581     if len(D)==1 or len(D[0])>1:
582        D1=[]     
583        for D0 in D:
584            D1+=unpack_DATA([D0])[:]
585        D=D1
586
587     #print "D2= :",D
588     return t,D
589
590
591 def scan_FILE(nom):
592   global CP, courbes, SCALE, DEBUG, BOUNDINGBOX, scale
593   dir,name=split(nom)
594   name=name.split('.')
595   n0=0
596   result=0
597   
598   t=filtreFICHIER(nom)
599   
600   if t!='false':
601      if not SHARP_IMPORT:
602          warning = "Select Size : %t| As is %x1 | Scale on Height %x2| Scale on Width %x3" 
603          scale = Blender.Draw.PupMenu(warning)
604      npat=0
605      l=0
606      do=0
607      t,SVG=get_tag('svg',t)
608
609      SVG,viewbox=get_content('viewBox',SVG)
610
611      SVG=SVG.split(' ')
612      if viewbox==0:
613           BOUNDINGBOX = get_BOUNDBOX(BOUNDINGBOX,SVG,0)
614      else:
615           BOUNDINGBOX = get_BOUNDBOX(BOUNDINGBOX,SVG,viewbox)     
616
617      #print t
618
619      while t.find('path')!=-1:
620          t,D=format_PATH(t)
621          cursor=0
622          for cell in D: 
623             if DEBUG==2 : print 'cell : ',cell ,' --'                   
624             if len(cell)>=1 and cell[0] in TAGcourbe:
625                    courbes,n0,CP=Actions[cell]([cell,cursor], D, n0,CP)            
626             cursor+=1
627
628   courbes.number_of_items=len(courbes.ITEM.keys())
629
630   for k in courbes.ITEM.keys():
631      courbes.ITEM[k].pntsUV[0] =len(courbes.ITEM[k].beziers_knot)
632
633   if courbes.number_of_items>0:
634      if len(PATTERN.keys() )>0:
635         if DEBUG == 3 : print len(PATTERN.keys() )
636      t=create_GEOtext(courbes)
637      save_GEOfile(dir,name[0],t)
638      Open_GEOfile(dir,name[0])
639   else:
640       pass
641     
642 def  ajustement(v,s):
643      
644      a,b,c,d,e,f=float(v.co[0]),float(v.co[1]),float(v.co[2]),float(v.co[3]),float(v.co[4]),float(v.co[5])
645      return [a/s,-b/s,c/s,-d/s,e/s,-f/s]
646
647 #=====================================================================
648 #====================== SVG format mouvements ========================
649 #=====================================================================
650
651 #=====================================================================
652 # une sorte de contournement qui permet d'utiliser la fonction
653 # et de documenter les variables Window.FileSelector
654 #=====================================================================
655 def fonctionSELECT(nom):
656     scan_FILE(nom)
657
658 if DEVELOPPEMENT==1:
659    Blender.Window.FileSelector (fonctionSELECT, 'SELECT a .SVG FILE')
660    #sys.path=oldpath