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

Last change on this file since 1060 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
RevLine 
[497]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",
[498]6    "category": "Import-Export",
[499]7    "description": "Imports POV-Ray files generated by Framsticks and exports Blender camera to POV-Ray files",
[498]8    "wiki_url": "http://www.framsticks.com/3d-animations-in-blender"
9}
[497]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]
[629]71    if val.find('.')!=-1 or val.find('e')!=-1:
[497]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:
[499]142    def __init__(self,nom='cylinderModel',type='cyl',r1=0.1, r2=0.1,h=1.0, n=8,smooth=1):
[497]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):
[578]175    global Folder, Files, Current, RecentlyCreated, RecentlyDisappeared
[497]176    Incoming=analysePOV(os.path.join(Folder,Files[num]))
[578]177    RecentlyCreated=[]
178    RecentlyDisappeared=[]
[497]179    for oname,obj in Incoming.items():
180        if not oname in Current:
181            # add object properties
182            print('Creature added:',oname)
[578]183            RecentlyCreated.append(oname)
[497]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)
[578]221    for oname,obj in Current.items():
222        if not oname in Incoming:
223            RecentlyDisappeared.append(oname)
224            print('Creature disappeared:',oname)
[497]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
[578]231       global  RecentlyCreated, RecentlyDisappeared
[497]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]))
[578]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)
[497]284
[578]285
[497]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
[630]306def povUpdateScene(obj,writepath):
[497]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
[630]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
[497]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
[630]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)
[497]339
[630]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
[497]350####################################
351
352class FramsticksPOVImporter(bpy.types.Operator, ImportHelper):
[499]353    """Load a collection of Framsticks POV files"""
[497]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')
[630]395        row = layout.row()
396        row.operator("framspov.writecamerapath",icon='SCRIPT')
[497]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):
[630]404        povUpdateScene(context.object,False)
[497]405        return{'FINISHED'}
406
[630]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
[497]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)
[630]421    bpy.utils.register_class(VIEW3D_OT_WritePOVCameraPath)
[497]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)
[630]428    bpy.utils.unregister_class(VIEW3D_OT_WritePOVCameraPath)
[497]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.