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