source: blender-povray-animation/frams-pov-import.py @ 797

Last change on this file since 797 was 630, checked in by sz, 8 years ago

added the option to write a separate "camerapath.inc" instead of updating the source ".pov" files

File size: 15.5 KB
Line 
1bl_info = {
2    "name": "Framsticks POV import & camera manipulator",
3    "author": "Szymon Ulatowski, jm soler",
4    "blender": (2, 70, 0),
5    "location": "File > Import-Export",
6    "category": "Import-Export",
7    "description": "Imports POV-Ray files generated by Framsticks and exports Blender camera to POV-Ray files",
8    "wiki_url": "http://www.framsticks.com/3d-animations-in-blender"
9}
10
11import os, math, bpy, re, copy, functools
12from bpy_extras.io_utils import ImportHelper
13from bpy.props import (CollectionProperty, StringProperty, BoolProperty, EnumProperty, FloatProperty)
14
15re_field=re.compile('^#declare field_([^_]+)_([^=]+)=(.*)$')
16re_object=re.compile('^BeginObject\(\)$')
17re_part=re.compile('^BeginObject\(\)$')
18re_partgeo=re.compile('^PartGeometry\(<([^>]+)>,<([^>]+)>\)$')
19re_joint=re.compile('^BeginJoint\(([0-9]+),([0-9]+)\)$')
20re_jointgeo=re.compile('^JointGeometry\(<([^>]+)>,<([^>]+)>,<([^>]+)>\)$')
21re_neuro=re.compile('^BeginNeuro\(([^)])\)$')
22
23Folder = ""
24FirstPOV=-1
25LastPOV=-1
26CREATURES={}
27
28# assumes the filename is "*_number.pov"
29def numFromFilename(f):
30    try:
31        return int(f[f.find('_')+1:f.find('.')])
32    except:
33        return -1
34
35# creates a global dictionary of filenames ("*_number.pov"), where key=number, updates FirstPOV/LastPOV
36def scanDir(startfile):
37    global Files, Folder, FileName
38    global FirstPOV, LastPOV
39
40    Files={}
41    Folder, FileName=os.path.split(startfile)
42
43    #print("startfile=",startfile,"Folder=",Folder,"FileName=",FileName)
44
45    underscore=FileName.find("_")
46    if underscore==-1:
47        return
48    if FileName.find(".pov")==-1:
49        return
50
51    FirstPOV=numFromFilename(FileName)
52    LastPOV=FirstPOV
53
54    for f in os.listdir(Folder):
55        if not f.endswith('.pov'):
56            continue
57        if not f.startswith(FileName[:underscore+1]):
58            continue
59        num=numFromFilename(f)
60        if num<0:
61            continue
62        Files[num]=f
63        if num>LastPOV:
64            LastPOV=num
65    #print("N=",len(Files))
66
67def extractValue(val):
68    if val[0]=='"':
69        # "string"
70        return val[1:-1]
71    if val.find('.')!=-1 or val.find('e')!=-1:
72       # floating point
73        return float(val)
74    else:
75       # integer
76       return int(val)
77
78def floatList(str):
79    return [float(x) for x in str.split(',')]
80
81def analysePOV(fname):
82    global re_field,re_object,re_part,re_partgeo,re_joint,re_jointgeo,re_neuro
83    f=open(fname,'r',encoding='latin-1')
84    POVlines=f.readlines()
85    f.close()
86    tmpfields={}
87    objects={}
88    for line in POVlines:
89        m=re_field.match(line)
90        if m:
91            value=m.group(3)
92            if value.endswith(';'):
93                value=value[:-1]
94            value=extractValue(value)
95            objname=m.group(1) # creature,m,p,j,n
96            fieldname=m.group(2)
97            if not objname in tmpfields:
98                tmpfields[objname]={}
99            tmpfields[objname][fieldname]=value
100            #print("obj=",m.group(1)," name=",m.group(2)," value=",value)
101        m=re_object.match(line)
102        if m:
103            objkey=tmpfields['Creature']['name']+'_'+tmpfields['Creature']['uid']
104            objkey=objkey.replace(' ','_')
105            recentobj={'fields':copy.deepcopy(tmpfields['Creature']),'parts':[],'joints':[],'neurons':[]}
106            objects[objkey]=recentobj
107        m=re_jointgeo.match(line)
108        if m:
109            joint={'StickLoc1':floatList(m.group(1)),
110                   'StickLoc2':floatList(m.group(2)),
111                   'StickRot':floatList(m.group(3)),
112                   'fields':copy.deepcopy(tmpfields['j'])}
113            #print(joint)
114            recentobj['joints'].append(joint)
115        m=re_partgeo.match(line)
116        if m:
117            part={'Loc':floatList(m.group(1)),
118                   'Rot':floatList(m.group(2)),
119                   'fields':copy.deepcopy(tmpfields['p'])}
120            #print(joint)
121            recentobj['parts'].append(part)
122    #print(tmpfields)
123    #print(objects)
124    #print(json.dumps(objects,indent=4))
125    return objects
126
127# vector length
128def vecLength(p1,p2):
129    p2[0]=p2[0]-p1[0]
130    p2[1]=p2[1]-p1[1]
131    p2[2]=p2[2]-p1[2]
132    return (p2[0]**2+p2[1]**2+p2[2]**2)**0.5
133
134def vecSub(p1,p2):
135    return [p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2]]
136
137def vecMul(p1,m):
138    return [p1[0]*m, p1[1]*m, p1[2]*m]
139
140# create an object containing reference to blender cylinder object
141class cylindre:
142    def __init__(self,nom='cylinderModel',type='cyl',r1=0.1, r2=0.1,h=1.0, n=8,smooth=1):
143          me=bpy.data.meshes.new(name=nom)
144          r=[r1,r2]
145          verts=[]
146          for i in range(0,2):
147              for j in range(0,n):
148                  z=math.sin(j*math.pi*2/(n))*r[i]
149                  y=math.cos(j*math.pi*2/(n))*r[i]
150                  x=float(i)*h
151                  verts.append((x,y,z))
152
153          vlist=[v for v in range(n)]
154          vlist.append(0)
155          faces=[]
156          for i in range(n):
157              faces.append((vlist[i],vlist[i+1],vlist[i+1]+n,vlist[i]+n))
158
159          if type=='cyl':
160              pos=[[0.0,0.0,0.0],[0.0,0.0,h]]
161              verts.append((pos[0][0],pos[0][1],pos[0][2]))
162              verts.append((pos[1][0],pos[1][1],pos[1][2]))
163
164              for i in range(n):
165                  faces.append((vlist[i],vlist[i+1],len(vlist)-2))
166                  faces.append((vlist[i],vlist[i+1],len(vlist)-1))
167
168          me.from_pydata(verts,[],faces)
169          me.update()
170          self.objet=bpy.data.objects.new(nom,me)
171          bpy.context.scene.objects.link(self.objet)
172
173# build or update blender objects from a POV file
174def updateBlender(SceneParent,num):
175    global Folder, Files, Current, RecentlyCreated, RecentlyDisappeared
176    Incoming=analysePOV(os.path.join(Folder,Files[num]))
177    RecentlyCreated=[]
178    RecentlyDisappeared=[]
179    for oname,obj in Incoming.items():
180        if not oname in Current:
181            # add object properties
182            print('Creature added:',oname)
183            RecentlyCreated.append(oname)
184            newobj=[] # will contain: [ parent, joint0, joint1, ... ]
185            Current[oname]=newobj
186            # create new blender objects
187            bcrea=bpy.data.objects.new(oname,None)
188            bpy.context.scene.objects.link(bcrea)
189            bcrea.parent=SceneParent
190            newobj.append(bcrea)
191            for j in obj['joints']:
192                cyl=cylindre(oname+'_j','tube',0.1,0.1,1.0,6)
193                cyl.objet.parent=bcrea
194                newobj.append(cyl.objet)
195
196        # update blender loc/rot/scale
197        existing_b=Current[oname]
198        if len(obj['joints']):
199            avg_loc=vecMul(functools.reduce(lambda a,b: [a[0]+b[0],a[1]+b[1],a[2]+b[2]], [j['StickLoc1'] for j in obj['joints']]),1/len(obj['joints']))
200        elif len(obj['parts']):
201            avg_loc=vecMul(functools.reduce(lambda a,b: [a[0]+b[0],a[1]+b[1],a[2]+b[2]], [p['Loc'] for p in obj['parts']]),1/len(obj['parts']))
202        else:
203            avg_loc=[0,0,0]
204        if len(existing_b)>0:
205            existing_b[0].location=avg_loc
206            existing_b[0].keyframe_insert(data_path='location',frame=bpy.context.scene.frame_current)
207        for i in range(len(obj['joints'])):
208            if i>=(len(existing_b)-1):
209                continue # number of joints has changed -> ignore
210            incoming_geo=obj['joints'][i]
211            #print('incoming:',incoming_geo)
212            bo=existing_b[i+1] # blender object
213            scale=[vecLength(incoming_geo['StickLoc1'],incoming_geo['StickLoc2']), 1.0, 1.0]
214            for xyz in [0,1,2]:
215                getattr(bo,'location')[xyz]=vecSub(incoming_geo['StickLoc1'],avg_loc)[xyz]
216                #getattr(bo,'location')[xyz]=incoming_geo['StickLoc1'][xyz]
217                getattr(bo,'rotation_euler')[xyz]=incoming_geo['StickRot'][xyz]
218                getattr(bo,'scale')[xyz]=scale[xyz]
219            for field in ['location','rotation_euler','scale']:
220                bo.keyframe_insert(data_path=field,frame=bpy.context.scene.frame_current)
221    for oname,obj in Current.items():
222        if not oname in Incoming:
223            RecentlyDisappeared.append(oname)
224            print('Creature disappeared:',oname)
225
226# import a sequence of POV files, create object hiererchy, animate
227def framsImport(startfile):
228       global  FirstPOV, LastPOV, Files
229       global  Folder, FileName
230       global  Current, FirstFrame, FrameCount
231       global  RecentlyCreated, RecentlyDisappeared
232       global  SceneParent
233       global  SkipFrames
234
235       scanDir(startfile)
236
237       if len(Files)<1:
238           print("No files found")
239           return
240
241       bpy.context.scene.frame_end=max(bpy.context.scene.frame_end,FirstFrame+FrameCount-1)
242
243       SceneParent=bpy.data.objects.new("Framsticks_"+str(FirstFrame),None)
244       bpy.context.scene.objects.link(SceneParent)
245       SceneParent.framspov_file=startfile
246       SceneParent.framspov_frame=FirstFrame
247       SceneParent.framspov_count=FrameCount
248
249       Current={}
250       NextSkip=0
251       for k in sorted(Files.keys()):
252           if k<NextSkip:
253               continue
254           NextSkip=k+SkipFrames
255           bpy.context.scene.frame_set(FirstFrame-FirstPOV+k)
256           if bpy.context.scene.frame_current >= FirstFrame+FrameCount:
257               break
258           print("Frame %d - loading POV %s" % (bpy.context.scene.frame_current,Files[k]))
259           updateBlender(SceneParent,k)
260           if len(RecentlyDisappeared)>0 or len(RecentlyCreated)>0:
261               bpy.context.scene.frame_set(FirstFrame-FirstPOV+k-1)
262               for oname in RecentlyCreated:
263                   obj=Current[oname]
264                   for bo in obj:
265                       bo.hide=True
266                       bo.keyframe_insert(data_path="hide",frame=bpy.context.scene.frame_current)
267               for oname in RecentlyDisappeared:
268                   obj=Current[oname]
269                   for bo in obj:
270                       bo.hide=False
271                       bo.keyframe_insert(data_path="hide",frame=bpy.context.scene.frame_current)
272               bpy.context.scene.frame_set(FirstFrame-FirstPOV+k)
273               for oname in RecentlyCreated:
274                   obj=Current[oname]
275                   for bo in obj:
276                       bo.hide=False
277                       bo.keyframe_insert(data_path="hide",frame=bpy.context.scene.frame_current)
278               for oname in RecentlyDisappeared:
279                   obj=Current[oname]
280                   for bo in obj:
281                       bo.hide=True
282                       bo.keyframe_insert(data_path="hide",frame=bpy.context.scene.frame_current)
283                   Current.pop(oname)
284
285
286###############################
287
288def povUpdateFile(filename,cam):
289    f=open(filename,'r',encoding='latin-1')
290    lines=f.readlines()
291    f.close()
292    for i in range(len(lines)):
293        line=lines[i]
294        if line.startswith('Camera('):
295            line='Camera(<%g,%g,%g>,<%g,%g,%g>)\n' % tuple(cam);
296            lines[i]=line
297    f=open(filename,'w',encoding='latin-1')
298    f.writelines(lines)
299    f.close()
300
301def framsCameraFromObj(obj):
302    #print(obj.location.x)
303    m=obj.matrix_local
304    return [obj.location.x, obj.location.y, obj.location.z, obj.location.x-m[0][2], obj.location.y-m[1][2], obj.location.z-m[2][2]]
305
306def povUpdateScene(obj,writepath):
307    global Folder,FirstFrame,FrameCount,Files,FirstPOV
308    print("Updating scene %s" % obj.name)
309    scanDir(obj.framspov_file)
310    if len(Files)<1:
311        print("No files found for "+obj.name)
312        return
313
314    if writepath:
315        f=open(os.path.join(Folder,'camerapath.inc'),'w',encoding='latin-1')
316        f.write("#local CameraPathFirst=1;\n")
317        f.write("#local CameraPath=array["+str(len(Files))+"*2]\n")
318        f.write("{\n")
319
320    FirstFrame=obj.framspov_frame
321    FrameCount=obj.framspov_count
322    for k in sorted(Files.keys()):
323        #bpy.context.scene.frame_current=FirstFrame-FirstPOV+k
324        bpy.context.scene.frame_set(FirstFrame-FirstPOV+k)
325        if bpy.context.scene.frame_current >= FirstFrame+FrameCount:
326            break
327        print("Frame %d - updating camera in %s" % (bpy.context.scene.frame_current,Files[k]))
328        cam=framsCameraFromObj(bpy.context.scene.camera)
329        cam[0]-=obj.location.x
330        cam[1]-=obj.location.y
331        cam[2]-=obj.location.z
332        cam[3]-=obj.location.x
333        cam[4]-=obj.location.y
334        cam[5]-=obj.location.z
335        if writepath:
336            f.write(("  <%g,%g,%g>,<%g,%g,%g>," % tuple(cam))+" //"+Files[k]+"\n")
337        else:
338            povUpdateFile(os.path.join(Folder,Files[k]),cam)
339
340    if writepath:
341        f.write('''
342}
343#if ((AnimFrame>=CameraPathFirst) & ((AnimFrame-CameraPathFirst)<(dimension_size(CameraPath,1)/2)))
344  #local i=2*(AnimFrame-CameraPathFirst);
345  Camera(CameraPath[i],CameraPath[i+1])
346#end
347''')
348        f.close()
349
350####################################
351
352class FramsticksPOVImporter(bpy.types.Operator, ImportHelper):
353    """Load a collection of Framsticks POV files"""
354    bl_idname = "framspov.import"
355    bl_label = "Import Framsticks POV"
356    bl_options = {'UNDO'}
357
358    files = CollectionProperty(name="File Path",
359                          description="File path used for importing",
360                          type=bpy.types.OperatorFileListElement)
361    directory = StringProperty()
362
363    framspov_skip = bpy.props.IntProperty(name="Frame step",min=1,max=100)
364
365    filename_ext = ".pov"
366    filter_glob = StringProperty(default="*.pov", options={'HIDDEN'})
367
368    def execute(self, context):
369        global FirstFrame, FrameCount,FileName,SkipFrames
370        FirstFrame = bpy.context.scene.frame_current
371        FrameCount = 9999
372        SkipFrames = self.framspov_skip
373        framsImport(os.path.join(self.directory, self.files[0].name))
374        return {'FINISHED'}
375   
376
377def menu_func_import(self, context):
378    self.layout.operator(FramsticksPOVImporter.bl_idname, text="Framsticks POV (.pov)")
379
380class OBJECT_PT_framspov(bpy.types.Panel):
381    bl_label = "Framsticks POV"
382    bl_space_type = "PROPERTIES"
383    bl_region_type = "WINDOW"
384    bl_context = "object"
385 
386    @classmethod
387    def poll(cls, context):
388        obj=context.object
389        return obj.framspov_file!=''
390 
391    def draw(self, context):
392        layout = self.layout
393        row = layout.row()
394        row.operator("framspov.updatecam",icon='SCRIPT')
395        row = layout.row()
396        row.operator("framspov.writecamerapath",icon='SCRIPT')
397
398
399class VIEW3D_OT_UpdatePOVCamera(bpy.types.Operator):
400    bl_idname = "framspov.updatecam"
401    bl_label = "Update POV camera"
402 
403    def execute(self, context):
404        povUpdateScene(context.object,False)
405        return{'FINISHED'}
406
407class VIEW3D_OT_WritePOVCameraPath(bpy.types.Operator):
408    bl_idname = "framspov.writecamerapath"
409    bl_label = "Write camerapath.inc"
410 
411    def execute(self, context):
412        povUpdateScene(context.object,True)
413        return{'FINISHED'}
414
415def register():
416    bpy.types.Object.framspov_file=bpy.props.StringProperty(name="Name of the first POV file")
417    bpy.types.Object.framspov_frame=bpy.props.IntProperty(name="First frame",min=0,max=999999)
418    bpy.types.Object.framspov_count=bpy.props.IntProperty(name="Number of frames",min=0,max=999999)
419    bpy.utils.register_class(FramsticksPOVImporter)
420    bpy.utils.register_class(VIEW3D_OT_UpdatePOVCamera)
421    bpy.utils.register_class(VIEW3D_OT_WritePOVCameraPath)
422    bpy.utils.register_class(OBJECT_PT_framspov)
423    bpy.types.INFO_MT_file_import.append(menu_func_import)
424
425def unregister():
426    bpy.utils.unregister_class(FramsticksPOVImporter)
427    bpy.utils.unregister_class(VIEW3D_OT_UpdatePOVCamera)
428    bpy.utils.unregister_class(VIEW3D_OT_WritePOVCameraPath)
429    bpy.utils.unregister_class(OBJECT_PT_framspov)
430    bpy.types.INFO_MT_file_import.remove(menu_func_import)
431
Note: See TracBrowser for help on using the repository browser.