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

Last change on this file since 497 was 497, checked in by Maciej Komosinski, 8 years ago

Added a script that imports POV-Ray files exported from Framsticks to blender

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