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