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

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

object creation and removal is now reflected in the blender scene view (using the "hide" property keyframes)

File size: 14.4 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:
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):
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    FirstFrame=obj.framspov_frame
314    FrameCount=obj.framspov_count
315    for k in sorted(Files.keys()):
316        #bpy.context.scene.frame_current=FirstFrame-FirstPOV+k
317        bpy.context.scene.frame_set(FirstFrame-FirstPOV+k)
318        if bpy.context.scene.frame_current >= FirstFrame+FrameCount:
319            break
320        print("Frame %d - updating camera in %s" % (bpy.context.scene.frame_current,Files[k]))
321        cam=framsCameraFromObj(bpy.context.scene.camera)
322        cam[0]-=obj.location.x
323        cam[1]-=obj.location.y
324        cam[2]-=obj.location.z
325        cam[3]-=obj.location.x
326        cam[4]-=obj.location.y
327        cam[5]-=obj.location.z
328        povUpdateFile(os.path.join(Folder,Files[k]),cam)
329
330####################################
331
332class FramsticksPOVImporter(bpy.types.Operator, ImportHelper):
333    """Load a collection of Framsticks POV files"""
334    bl_idname = "framspov.import"
335    bl_label = "Import Framsticks POV"
336    bl_options = {'UNDO'}
337
338    files = CollectionProperty(name="File Path",
339                          description="File path used for importing",
340                          type=bpy.types.OperatorFileListElement)
341    directory = StringProperty()
342
343    framspov_skip = bpy.props.IntProperty(name="Frame step",min=1,max=100)
344
345    filename_ext = ".pov"
346    filter_glob = StringProperty(default="*.pov", options={'HIDDEN'})
347
348    def execute(self, context):
349        global FirstFrame, FrameCount,FileName,SkipFrames
350        FirstFrame = bpy.context.scene.frame_current
351        FrameCount = 9999
352        SkipFrames = self.framspov_skip
353        framsImport(os.path.join(self.directory, self.files[0].name))
354        return {'FINISHED'}
355   
356
357def menu_func_import(self, context):
358    self.layout.operator(FramsticksPOVImporter.bl_idname, text="Framsticks POV (.pov)")
359
360class OBJECT_PT_framspov(bpy.types.Panel):
361    bl_label = "Framsticks POV"
362    bl_space_type = "PROPERTIES"
363    bl_region_type = "WINDOW"
364    bl_context = "object"
365 
366    @classmethod
367    def poll(cls, context):
368        obj=context.object
369        return obj.framspov_file!=''
370 
371    def draw(self, context):
372        layout = self.layout
373        row = layout.row()
374        row.operator("framspov.updatecam",icon='SCRIPT')
375
376
377class VIEW3D_OT_UpdatePOVCamera(bpy.types.Operator):
378    bl_idname = "framspov.updatecam"
379    bl_label = "Update POV camera"
380 
381    def execute(self, context):
382        povUpdateScene(context.object)
383        return{'FINISHED'}
384
385def register():
386    bpy.types.Object.framspov_file=bpy.props.StringProperty(name="Name of the first POV file")
387    bpy.types.Object.framspov_frame=bpy.props.IntProperty(name="First frame",min=0,max=999999)
388    bpy.types.Object.framspov_count=bpy.props.IntProperty(name="Number of frames",min=0,max=999999)
389    bpy.utils.register_class(FramsticksPOVImporter)
390    bpy.utils.register_class(VIEW3D_OT_UpdatePOVCamera)
391    bpy.utils.register_class(OBJECT_PT_framspov)
392    bpy.types.INFO_MT_file_import.append(menu_func_import)
393
394def unregister():
395    bpy.utils.unregister_class(FramsticksPOVImporter)
396    bpy.utils.unregister_class(VIEW3D_OT_UpdatePOVCamera)
397    bpy.utils.unregister_class(OBJECT_PT_framspov)
398    bpy.types.INFO_MT_file_import.remove(menu_func_import)
399
Note: See TracBrowser for help on using the repository browser.