├── LICENSE ├── advancedfx_export_bvh.py ├── readme.txt ├── advancedfx_import_bvh.py └── advancedfx_import_gameRecord.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 advancedfx.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /advancedfx_export_bvh.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) advancedfx.org 2 | # 3 | # Last changes: 4 | # 2016-06-28 by dominik.matrixstorm.com 5 | # 6 | # First changes: 7 | # 2009-09-03 by dominik.matrixstorm.com 8 | 9 | 10 | # 57.29577951308232087679815481410... 11 | RAD2DEG = 57.2957795130823208767981548141 12 | 13 | 14 | 15 | import sfm; 16 | import sfmUtils; 17 | import sfmApp; 18 | from PySide import QtGui 19 | 20 | 21 | def SetError(error): 22 | print 'ERROR:', error 23 | QtGui.QMessageBox.warning( None, "ERROR:", error ) 24 | 25 | 26 | # Formats a float value to be suitable for bvh output 27 | def FloatToBvhString(value): 28 | return "{0:f}".format(value) 29 | 30 | 31 | def WriteHeader(file, frames, frameTime): 32 | file.write("HIERARCHY\n") 33 | file.write("ROOT MdtCam\n") 34 | file.write("{\n") 35 | file.write("\tOFFSET 0.00 0.00 0.00\n") 36 | file.write("\tCHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n") 37 | file.write("\tEnd Site\n") 38 | file.write("\t{\n") 39 | file.write("\t\tOFFSET 0.00 0.00 -1.00\n") 40 | file.write("\t}\n") 41 | file.write("}\n") 42 | file.write("MOTION\n") 43 | file.write("Frames: "+str(frames)+"\n") 44 | file.write("Frame Time: "+FloatToBvhString(frameTime)+"\n") 45 | 46 | def LimDeg(val): 47 | return val 48 | 49 | def WriteFile(fileName, scale): 50 | shot = sfm.GetCurrentShot() 51 | animSet = sfm.GetCurrentAnimationSet() 52 | 53 | dag = sfm.FindDag("transform") 54 | 55 | if dag == None: 56 | SetError("Selected animation set does not have transform DAG node.") 57 | return False 58 | 59 | curFrame = 0 60 | fps = sfmApp.GetFramesPerSecond() 61 | frameTime = fps 62 | if not 0 == frameTime: 63 | frameTime = 1.0/float(frameTime) 64 | frameCount = shot.GetDuration().CurrentFrame(vs.DmeFramerate_t(fps)) 65 | 66 | file = open(fileName, 'wb') 67 | if not file: 68 | SetError('Could not open file '+fileName+' for writing') 69 | return False 70 | 71 | oldFrame = sfm.GetCurrentFrame() 72 | try: 73 | WriteHeader(file, frameCount, frameTime) 74 | 75 | while curFrame advancedfx_import_bvh 16 | 17 | It is possible to import into an camera with existing keyframes, keyframes 18 | in the imported timespan will be removed. 19 | 20 | The import will not set the correct FOV for the camera (the BVH file also does 21 | not contain FOV information), that is left up to the user. 22 | Also be aware that CS:GO actually uses an higher FOV than you set in-game: 23 | http://advancedfx.style-productions.net/forum/viewtopic.php?f=17&t=1811 24 | 25 | 26 | 27 | 28 | How to use advancedfx_export_bvh: 29 | 30 | 1. Select a clip 31 | 5. Right click the camera you want to export in Animation Set Editor and 32 | select Rig -> advancedfx_export_bvh 33 | 34 | The FPS exported is determined by your project output FPS. 35 | 36 | 37 | 38 | 39 | How to use advancedfx_import_gameRecord: 40 | 41 | Create an afxGameRecording in HLAE / AfxHookSource, preferably with 42 | low FPS / host_framerate (i.e. 30) and not too long, 43 | because otherwise you will run out of memory upon importing 44 | into SFM! 45 | You can do that using the "mirv_streams record agr" command in HLAE / 46 | AfxHookSource. 47 | 48 | To import the recording in SFM: 49 | 50 | 1. Create an dummy animation set (i.e. camera) on the clip where you want to 51 | import 52 | 2. Right click the created set and in Animation Set Editor and 53 | select Rig -> advancedfx_import_gameRecord 54 | 55 | Attention: 56 | 57 | Automatic importing of faulty / broken models (i.e. from the 58 | models\player\custom_player\legacy folder, which is used in older de_cache 59 | demos for example) will cause memory 60 | corruptions that usually lead to a crash of SFM. 61 | To avoid those crashes, don't make such models available to SFM! 62 | 63 | Notes: 64 | 65 | In the current version keyframes will only be created when the 66 | gameModel is visible, otherwise we save some memory. 67 | 68 | Currently this feature is mainly meant to import player models. 69 | It will import some viewmodels too, however those 70 | might be missing arms or have the Error model in SFM, due to being 71 | a custom (skin) model (modelName is '?' then). 72 | 73 | 74 | 75 | Changelog: 76 | 77 | 1.7.2 (2022-07-03T13:03Z): 78 | - advancedfx_import_gameRecord: 79 | - Updated to support AGR version 6. 80 | Please note: AGR version 6 can express more than SFM in terms of matrices for models and model bones, 81 | if the matrices can not be converted to vector position + quaternion rotation, then there will be 82 | problems. E.g. mirrored (view) models would be a problem. 83 | 84 | 1.7.1 (2020-12-14T18:18Z): 85 | - advancedfx_import_gameRecord: 86 | - Fixed error in script that could cause import to fail in some conditions. 87 | 88 | 1.7.0 (2020-08-11T19:14Z): 89 | - advancedfx_import_gameRecord: 90 | - Updated to AGR version 5 91 | 92 | 1.6.0 (2017-09-16T22:00Z): 93 | - advancedfx_import_gameRecord: 94 | - Updated to AGR version 4 95 | 96 | 1.5.0 (2017-09-13T17:26Z): 97 | - advancedfx_import_gameRecord: 98 | - Updated to AGR version 3 99 | 100 | 101 | 1.4.0 (2017-08-03T12:00Z): 102 | - advancedfx_import_gameRecord: 103 | - Updated to agr version 2 (also bug fixes) 104 | 105 | 1.3.0 (2017-06-26T05:07Z): 106 | - advancedfx_import_gameRecord: 107 | - Updated to agr version 1 108 | 109 | 1.2.1 (2016-07-16T10:43Z): 110 | - advancedfx_import_gameRecord: 111 | - Now sets gameModel.evaluateProceduralBones = False, to avoid player 112 | models wrapping and hopefully also avoid crashing SFM (this is 113 | relevant for demos using the new models in 114 | 115 | 1.2.0 (2016-07-15T13:37Z): 116 | - advancedfx_import_gameRecord: 117 | - Improved for AfxHookSource 1.6.0, now handles entity delete envents 118 | properly and model switching. 119 | 120 | 1.1.0 (2016-07-15T13:37Z): 121 | - advancedfx_import_gameRecord: 122 | - First version 123 | - advancedfx_import_bvh: 124 | - Does not create bookmarks for keys anymore, in order to save memory 125 | (you can add and edit bookmarks manually). 126 | 127 | 1.0.1 (2016-06-30T19:10Z): 128 | - advancedfx_import_bvh: 129 | - Now un-pushes the Snap / Snap Frame tool buttons in timeline if required. 130 | 131 | 1.0.0 (2016-06-28T21:16Z): 132 | - advancedfx_import_bvh: 133 | - Minor imporvements 134 | - advancedfx_export_bvh: 135 | - First version 136 | 137 | 0.1.0 (2016-06-28T06:07Z): 138 | - advancedfx_import_bvh: 139 | - Fixed awkward camera movment / rolling 140 | - Now allows to import into existing graph data (so you can append / insert 141 | into existing camera) 142 | 143 | 0.0.1 (2016-06-27T20:58Z): 144 | - advancedfx_import_bvh: 145 | - First version 146 | -------------------------------------------------------------------------------- /advancedfx_import_bvh.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) advancedfx.org 2 | # 3 | # Last changes: 4 | # 2016-07-14 by dominik.matrixstorm.com 5 | # 6 | # First changes: 7 | # 2009-09-01 by dominik.matrixstorm.com 8 | 9 | 10 | # DEG2RAD = 0.0174532925199432957692369076849 11 | # PI = 3.14159265358979323846264338328 12 | 13 | 14 | import sfm; 15 | import sfmUtils; 16 | import sfmApp; 17 | from PySide import QtGui 18 | 19 | 20 | def SetError(error): 21 | print 'ERROR:', error 22 | QtGui.QMessageBox.warning( None, "ERROR:", error ) 23 | 24 | 25 | # reads a line from file and separates it into words by splitting whitespace 26 | # file to read from 27 | # list of words 28 | def ReadLineWords(file): 29 | line = file.readline() 30 | words = [ll for ll in line.split() if ll] 31 | return words 32 | 33 | 34 | # searches a list of words for a word by lower case comparison 35 | # list to search 36 | # word to find 37 | # less than 0 if not found, otherwise the first list index 38 | def FindWordL(words, word): 39 | i = 0 40 | word = word.lower() 41 | while i < len(words): 42 | if words[i].lower() == word: 43 | return i 44 | i += 1 45 | return -1 46 | 47 | 48 | # Scans the file till a line containing a lower case match of filterWord is found 49 | # file to read from 50 | # word to find 51 | # False on fail, otherwise same as ReadLineWords for this line 52 | def ReadLineWordsFilterL(file, filterWord): 53 | while True: 54 | words = ReadLineWords(file) 55 | if 0 < len(words): 56 | if 0 <= FindWordL(words, filterWord): 57 | return words 58 | else: 59 | return False 60 | 61 | 62 | # Scans the file till the channels line and reads channel information 63 | # file to read from 64 | # False on fail, otherwise channel indexes as follows: [Xposition, Yposition, Zposition, Zrotation, Xrotation, Yrotation] 65 | def ReadChannels(file): 66 | words = ReadLineWordsFilterL(file, 'CHANNELS') 67 | 68 | if not words: 69 | return False 70 | 71 | channels = [\ 72 | FindWordL(words, 'Xposition'),\ 73 | FindWordL(words, 'Yposition'),\ 74 | FindWordL(words, 'Zposition'),\ 75 | FindWordL(words, 'Zrotation'),\ 76 | FindWordL(words, 'Xrotation'),\ 77 | FindWordL(words, 'Yrotation')\ 78 | ] 79 | 80 | idx = 0 81 | while idx < 6: 82 | channels[idx] -= 2 83 | idx += 1 84 | 85 | for channel in channels: 86 | if not (0 <= channel and channel < 6): 87 | return False 88 | 89 | return channels 90 | 91 | 92 | def ReadRootName(file): 93 | words = ReadLineWordsFilterL(file, 'ROOT') 94 | 95 | if not words or len(words)<2: 96 | return False 97 | 98 | return words[1] 99 | 100 | 101 | def ReadFrames(file): 102 | words = ReadLineWordsFilterL(file, 'Frames:') 103 | 104 | if not words or len(words)<2: 105 | return -1 106 | 107 | return int(words[1]) 108 | 109 | def ReadFrameTime(file): 110 | words = ReadLineWordsFilterL(file, 'Time:') 111 | 112 | if not words or len(words)<3: 113 | return -1 114 | 115 | return float(words[2]) 116 | 117 | 118 | def ReadFrame(file, channels): 119 | line = ReadLineWords(file) 120 | 121 | if len(line) < 6: 122 | return False; 123 | 124 | Xpos = float(line[channels[0]]) 125 | Ypos = float(line[channels[1]]) 126 | Zpos = float(line[channels[2]]) 127 | Zrot = float(line[channels[3]]) 128 | Xrot = float(line[channels[4]]) 129 | Yrot = float(line[channels[5]]) 130 | 131 | return [Xpos, Ypos, Zpos, Zrot, Xrot, Yrot] 132 | 133 | 134 | def ClearBookmarks(log, component, start, end): 135 | idx = 0 136 | while idx < log.GetNumBookmarks(component): 137 | time = log.GetBookmarkTime(component, idx) 138 | if(start <= time and time <= end): 139 | log.RemoveBookmark(component, time) 140 | else: 141 | idx = idx + 1 142 | 143 | 144 | def ClearKeys(log, start, end): 145 | idx = 0 146 | while idx < log.GetKeyCount(): 147 | time = log.GetKeyTime(idx) 148 | if(start <= time and time <= end): 149 | log.RemoveKey(idx) 150 | else: 151 | idx = idx + 1 152 | 153 | 154 | def ReadFile(fileName, scale, camFov): 155 | shot = sfm.GetCurrentShot() 156 | animSet = sfm.GetCurrentAnimationSet() 157 | 158 | channelsClip = sfmUtils.GetChannelsClipForAnimSet(animSet, shot) 159 | 160 | if channelsClip == None: 161 | SetError("Selected animation set does not have channels clip.") 162 | return False 163 | 164 | rootControlGroup = animSet.GetRootControlGroup() 165 | 166 | if None == rootControlGroup: 167 | SetError("Selected animation set does not have rootControlGroup.") 168 | return False 169 | 170 | transformCtrl = rootControlGroup.FindControlByName("transform", True) 171 | 172 | if None == transformCtrl: 173 | SetError("Selected animation set does not have transform control.") 174 | return False 175 | 176 | positionChan = transformCtrl.GetPositionChannel() 177 | orientationChan = transformCtrl.GetOrientationChannel() 178 | 179 | file = open(fileName, 'rU') 180 | 181 | rootName = ReadRootName(file) 182 | if not rootName: 183 | SetError('Failed parsing ROOT.') 184 | return False 185 | 186 | print 'ROOT:', rootName 187 | 188 | channels = ReadChannels(file) 189 | if not channels: 190 | SetError('Failed parsing CHANNELS.') 191 | return False 192 | 193 | frames = ReadFrames(file); 194 | if frames < 0: 195 | SetError('Failed parsing Frames.') 196 | return False 197 | 198 | if 0 == frames: frames = 1 199 | 200 | frames = float(frames) 201 | 202 | frameTime = ReadFrameTime(file) 203 | if not frameTime: 204 | SetError('Failed parsing Frame Time.') 205 | return False 206 | 207 | timeOffset = (shot.ToChildMediaTime(vs.DmeTime_t(sfmApp.GetHeadTimeInSeconds()),False) -channelsClip.timeFrame.start.GetValue()) 208 | 209 | # Prepare curves: 210 | timeStart = timeOffset 211 | timeEnd = vs.DmeTime_t(float(frameTime) * float(frames)) +timeOffset 212 | ClearKeys(positionChan.log,timeStart,timeEnd) 213 | ClearBookmarks(positionChan.log,0,timeStart,timeEnd) 214 | ClearBookmarks(positionChan.log,1,timeStart,timeEnd) 215 | ClearBookmarks(positionChan.log,2,timeStart,timeEnd) 216 | ClearKeys(orientationChan.log,timeStart,timeEnd) 217 | ClearBookmarks(orientationChan.log,0,timeStart,timeEnd) 218 | ClearBookmarks(orientationChan.log,1,timeStart,timeEnd) 219 | ClearBookmarks(orientationChan.log,2,timeStart,timeEnd) 220 | 221 | frameCount = float(0) 222 | 223 | lastQuat = None 224 | 225 | while True: 226 | frame = ReadFrame(file, channels) 227 | if not frame: 228 | break 229 | 230 | frameCount += 1 231 | 232 | BTT = vs.DmeTime_t(float(frameTime) * float(frameCount-1)) +timeOffset 233 | 234 | BYP = -frame[0] *scale 235 | BZP = frame[1] *scale 236 | BXP = -frame[2] *scale 237 | 238 | BZR = -frame[3] 239 | BXR = -frame[4] 240 | BYR = frame[5] 241 | 242 | positionChan.log.SetKey(BTT, vs.Vector(BXP, BYP, BZP)) 243 | # positionChan.log.AddBookmark(BTT, 0) # We cannot afford bookmarks (waste of memory) 244 | # positionChan.log.AddBookmark(BTT, 1) # We cannot afford bookmarks (waste of memory) 245 | # positionChan.log.AddBookmark(BTT, 2) # We cannot afford bookmarks (waste of memory) 246 | 247 | quat = vs.Quaternion() 248 | vs.AngleQuaternion(vs.QAngle(BXR,BYR,BZR), quat) 249 | 250 | # Make sure we travel the short way: 251 | if lastQuat: 252 | dp = vs.QuaternionDotProduct(lastQuat,quat) 253 | if dp < 0: 254 | quat = vs.Quaternion(-quat.x,-quat.y,-quat.z,-quat.w) 255 | 256 | lastQuat = quat 257 | 258 | orientationChan.log.SetKey(BTT, quat) 259 | # orientationChan.log.AddBookmark(BTT, 0) # We cannot afford bookmarks (waste of memory) 260 | # orientationChan.log.AddBookmark(BTT, 1) # We cannot afford bookmarks (waste of memory) 261 | # orientationChan.log.AddBookmark(BTT, 2) # We cannot afford bookmarks (waste of memory) 262 | 263 | if not frameCount == frames: 264 | SetError("Frames are missing in BVH file.") 265 | return False 266 | 267 | return True 268 | 269 | def GetSnapButtons(): 270 | snap = None 271 | snapFrame = None 272 | for x in sfmApp.GetMainWindow().findChildren(QtGui.QToolButton): 273 | toolTip = x.toolTip() 274 | if "Snap" == toolTip: 275 | snap = x 276 | else: 277 | if "Snap Frame" == toolTip: 278 | snapFrame = x 279 | 280 | return snap, snapFrame 281 | 282 | def ImportCamera(): 283 | fileName, _ = QtGui.QFileDialog.getOpenFileName(None, "Open HLAE BVH File", "", "HLAE BVH (*.bvh)") 284 | if not 0 < len(fileName): 285 | return 286 | 287 | oldTimelineMode = sfmApp.GetTimelineMode() 288 | 289 | snap, snapFrame = GetSnapButtons() 290 | 291 | snapChecked = snap.isChecked() 292 | snapFrameChecked = snapFrame.isChecked() 293 | 294 | try: 295 | sfmApp.SetTimelineMode(3) # Work around timeline bookmark update bug (can't be in Graph Editor or it won't update) 296 | 297 | # Work around bug/feature causing programatically inserted keyframes to be snapped: 298 | if(snapChecked): 299 | snap.click() 300 | if(snapFrameChecked): 301 | snapFrame.click() 302 | 303 | if ReadFile(fileName, 1.0, 90.0): 304 | print 'Done.' 305 | else: 306 | print 'FAILED' 307 | 308 | finally: 309 | checked = "" 310 | if(snapFrameChecked): 311 | #snapFrame.click() # if we unheck here it will still snap ... 312 | checked = "'Snap Frame'" 313 | if(snapChecked): 314 | #snap.click() # if we uncheck here it will still snap ... 315 | if 0 < len(checked): 316 | checked = " and " + checked 317 | checked = "'Snap'" + checked 318 | 319 | if 0 < len(checked): 320 | QtGui.QMessageBox.information( None, "Attention", "Had to un-push " + checked + " tool button in timeline! Push again (if wanted)." ) 321 | 322 | sfmApp.SetTimelineMode(oldTimelineMode) 323 | 324 | ImportCamera() 325 | -------------------------------------------------------------------------------- /advancedfx_import_gameRecord.py: -------------------------------------------------------------------------------- 1 | 2 | # DEG2RAD = 0.0174532925199432957692369076849 3 | # PI = 3.14159265358979323846264338328 4 | 5 | import sfm 6 | import sfmUtils 7 | import sfmApp 8 | from PySide import QtGui 9 | import gc 10 | import struct 11 | import math 12 | 13 | def SetError(error): 14 | print 'ERROR:', error 15 | QtGui.QMessageBox.warning( None, "ERROR:", error ) 16 | 17 | # Slow as fuck! 18 | def FindChannel(channels,name): 19 | for i in channels: 20 | if name == i.GetName(): 21 | return i 22 | return None 23 | 24 | def InitalizeAnimSet(animSet,makeVisibleChannel = True): 25 | shot = sfm.GetCurrentShot() 26 | 27 | channelsClip = sfmUtils.GetChannelsClipForAnimSet(animSet, shot) 28 | 29 | visibleChannel = None 30 | 31 | if makeVisibleChannel: 32 | # Ensure additional channels: 33 | visibleChannel = FindChannel(channelsClip.channels,'visible_channel') 34 | if visibleChannel is None: 35 | visibleChannel = sfmUtils.CreateControlAndChannel('visible', vs.AT_BOOL, False, animSet, shot).channel 36 | visibleChannel.mode = 3 37 | visibleChannel.fromElement = animSet.gameModel 38 | visibleChannel.fromAttribute = 'visible' 39 | visibleChannel.toElement = animSet.gameModel 40 | visibleChannel.toAttribute = 'visible' 41 | 42 | # clear channel logs: 43 | for chan in channelsClip.channels: 44 | chan.ClearLog() 45 | 46 | if visibleChannel: 47 | # Not visible initially: 48 | visibleChannel.log.SetKey(-channelsClip.timeFrame.start.GetValue(), False) 49 | 50 | class ChannelCache: 51 | dict = {} 52 | 53 | def GetChannel(self,animSet,channelName): 54 | key = animSet.GetName() + channelName 55 | value = self.dict.get(key, False) 56 | if False == value: 57 | channelsClip = sfmUtils.GetChannelsClipForAnimSet(animSet, sfm.GetCurrentShot()) 58 | value = FindChannel(channelsClip.channels, channelName) 59 | self.dict[key] = value 60 | 61 | return value 62 | 63 | def MakeKeyFrameValue(channelCache,animSet,channelName,time,value): 64 | chan = channelCache.GetChannel(animSet, channelName) 65 | chan.log.SetKey(time, value) 66 | 67 | def MakeKeyFrameTransform(channelCache,animSet,channelName,time,vec,quat,shortestPath=False,posSuffix='_p',rotSuffix='_o'): 68 | positionChan = channelCache.GetChannel(animSet, channelName+posSuffix) 69 | orientationChan = channelCache.GetChannel(animSet, channelName+rotSuffix) 70 | 71 | positionChan.log.SetKey(time, vec) 72 | # positionChan.log.AddBookmark(time, 0) # We cannot afford bookmarks (waste of memory) 73 | # positionChan.log.AddBookmark(time, 1) # We cannot afford bookmarks (waste of memory) 74 | # positionChan.log.AddBookmark(time, 2) # We cannot afford bookmarks (waste of memory) 75 | 76 | if(shortestPath): 77 | # Make sure we travel the short way: 78 | lastQuatKeyIdx = orientationChan.log.FindKey(time) 79 | if(0 <= lastQuatKeyIdx and lastQuatKeyIdx < orientationChan.log.GetKeyCount()): 80 | lastQuat = orientationChan.log.GetValue(orientationChan.log.GetKeyTime(lastQuatKeyIdx)) 81 | dp = vs.QuaternionDotProduct(lastQuat,quat) 82 | if dp < 0: 83 | quat = vs.Quaternion(-quat.x,-quat.y,-quat.z,-quat.w) 84 | 85 | orientationChan.log.SetKey(time, quat) 86 | # orientationChan.log.AddBookmark(time, 0) # We cannot afford bookmarks (waste of memory) 87 | # orientationChan.log.AddBookmark(time, 1) # We cannot afford bookmarks (waste of memory) 88 | # orientationChan.log.AddBookmark(time, 2) # We cannot afford bookmarks (waste of memory) 89 | 90 | def QuaternionFromQAngle(qangle): 91 | quat = vs.Quaternion() 92 | vs.AngleQuaternion(qangle, quat) 93 | return quat 94 | 95 | def Quaternion(x,y,z,w): 96 | quat = vs.Quaternion() 97 | quat.x = x; 98 | quat.y = y; 99 | quat.z = z; 100 | quat.w = w 101 | return quat 102 | 103 | def ReadString(file): 104 | buf = bytearray() 105 | while True: 106 | b = file.read(1) 107 | if len(b) < 1: 108 | return None 109 | elif b == '\0': 110 | return str(buf) 111 | else: 112 | buf.append(b[0]) 113 | 114 | def ReadBool(file): 115 | buf = file.read(1) 116 | if(len(buf) < 1): 117 | return None 118 | return struct.unpack(' 60 else dagName 450 | dagName = "afx." +str(modelHandle.objNr)+ " " + dagName 451 | 452 | dagAnimSet = sfmUtils.CreateModelAnimationSet(dagName,modelName) 453 | 454 | if(hasattr(dagAnimSet,'gameModel')): 455 | dagAnimSet.gameModel.evaluateProceduralBones = False # This will look awkwardly and crash SFM otherwise 456 | 457 | print "Initalizing animSet " + dagName 458 | InitalizeAnimSet(dagAnimSet) 459 | 460 | modelHandle.modelData = dagAnimSet 461 | 462 | modelHandle.lastRenderOrigin = renderOrigin 463 | 464 | MakeKeyFrameValue(channelCache, dagAnimSet, 'visible_channel', timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(dagAnimSet, shot)), visible) 465 | 466 | MakeKeyFrameTransform(channelCache, dagAnimSet, "rootTransform", timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(dagAnimSet, shot)), renderOrigin, renderRotation, True) 467 | 468 | if dict.Peekaboo(file,'baseanimating'): 469 | #skin = ReadInt(file) 470 | #body = ReadInt(file) 471 | #sequence = ReadInt(file) 472 | hasBoneList = ReadBool(file) 473 | if hasBoneList: 474 | dagModel = None 475 | if dagAnimSet is not None and hasattr(dagAnimSet,'gameModel'): 476 | dagModel = dagAnimSet.gameModel 477 | 478 | numBones = ReadInt(file) 479 | 480 | for i in xrange(numBones): 481 | vec = vs.Vector(0,0,0) 482 | quat = vs.Quaternion(0,0,0,1) 483 | if version == 6: 484 | matrix3x4 = ReadMatrix3x4(file) 485 | vs.mathlib.MatrixPosition(matrix3x4,vec) 486 | vs.mathlib.MatrixQuaternion(matrix3x4,quat) 487 | else: 488 | vec = ReadVector(file) 489 | quat = ReadQuaternion(file) 490 | 491 | if dagModel is None: 492 | continue 493 | 494 | if(i < len(dagModel.bones)): 495 | name = dagModel.bones[i].GetName() 496 | #print name 497 | 498 | name = name[name.find('(')+1:] 499 | name = name[:name.find(')')] 500 | #print name 501 | 502 | MakeKeyFrameTransform(channelCache, dagAnimSet, name, timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(dagAnimSet, shot)), vec, quat) 503 | 504 | if dict.Peekaboo(file,'camera'): 505 | thidPerson = ReadBool(file) 506 | renderOrigin = ReadVector(file) 507 | renderAngles = ReadQAngle(file) 508 | fov = ReadFloat(file) 509 | fov = fov / 180.0 510 | 511 | modelCamera = modelHandle.camera 512 | if modelCamera is None: 513 | camName = "camera."+str(modelHandle.objNr) 514 | dmeAfxCam = vs.CreateElement( "DmeCamera", camName, shot.GetFileId()) 515 | modelCamera = sfm.CreateAnimationSet( camName, target=dmeAfxCam) 516 | InitalizeAnimSet(modelCamera,makeVisibleChannel=False) 517 | channelsClip = sfmUtils.GetChannelsClipForAnimSet(modelCamera, sfm.GetCurrentShot()) 518 | scaled_fieldOfView_channel = FindChannel(channelsClip.channels, "scaled_fieldOfView_channel") 519 | scaled_fieldOfView_channel.fromElement.lo = 0 520 | scaled_fieldOfView_channel.fromElement.hi = 180 521 | shot.scene.GetChild(shot.scene.FindChild("Cameras")).AddChild(dmeAfxCam) 522 | modelHandle.camera = modelCamera 523 | 524 | MakeKeyFrameValue(channelCache, modelCamera, 'fieldOfView', timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(modelCamera, shot)), fov) 525 | MakeKeyFrameTransform(channelCache, modelCamera, 'transform', timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(modelCamera, shot)), renderOrigin, QuaternionFromQAngle(renderAngles), True, '_pos', '_rot') 526 | 527 | dict.Peekaboo(file,'/') 528 | 529 | viewModel = ReadBool(file) 530 | 531 | elif 'afxCam' == node0: 532 | 533 | if afxCam is None: 534 | dmeAfxCam = vs.CreateElement( "DmeCamera", "afxCam", shot.GetFileId()) 535 | afxCam = sfm.CreateAnimationSet( "afxCam", target=dmeAfxCam) 536 | InitalizeAnimSet(afxCam,makeVisibleChannel=False) 537 | channelsClip = sfmUtils.GetChannelsClipForAnimSet(afxCam, sfm.GetCurrentShot()) 538 | scaled_fieldOfView_channel = FindChannel(channelsClip.channels, "scaled_fieldOfView_channel") 539 | scaled_fieldOfView_channel.fromElement.lo = 0 540 | scaled_fieldOfView_channel.fromElement.hi = 180 541 | shot.scene.GetChild(shot.scene.FindChild("Cameras")).AddChild(dmeAfxCam) 542 | 543 | renderOrigin = ReadVector(file) 544 | renderAngles = ReadQAngle(file) 545 | fov = ReadFloat(file) 546 | fov = fov / 180.0 547 | 548 | MakeKeyFrameValue(channelCache, afxCam, 'fieldOfView', timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(afxCam, shot)), fov) 549 | MakeKeyFrameTransform(channelCache, afxCam, 'transform', timeConverter.GetTime(sfmUtils.GetChannelsClipForAnimSet(afxCam, shot)), renderOrigin, QuaternionFromQAngle(renderAngles), True, '_pos', '_rot') 550 | 551 | else: 552 | SetError('Unknown packet: ') 553 | return False 554 | finally: 555 | if file is not None: 556 | file.close() 557 | 558 | return True 559 | 560 | def GetSnapButtons(): 561 | snap = None 562 | snapFrame = None 563 | for x in sfmApp.GetMainWindow().findChildren(QtGui.QToolButton): 564 | toolTip = x.toolTip() 565 | if "Snap" == toolTip: 566 | snap = x 567 | else: 568 | if "Snap Frame" == toolTip: 569 | snapFrame = x 570 | 571 | return snap, snapFrame 572 | 573 | def ImportGameRecord(): 574 | fileName, _ = QtGui.QFileDialog.getOpenFileName(None, "Open HLAE GameRecord File", "", "afxGameRecord (*.agr)") 575 | if not 0 < len(fileName): 576 | return 577 | 578 | oldTimelineMode = sfmApp.GetTimelineMode() 579 | 580 | snap, snapFrame = GetSnapButtons() 581 | 582 | snapChecked = snap.isChecked() 583 | snapFrameChecked = snapFrame.isChecked() 584 | 585 | try: 586 | gc.collect() # Free memory, since we need much of it 587 | 588 | #sfm.SetOperationMode("Record") 589 | sfmApp.SetTimelineMode(3) # Work around timeline bookmark update bug (can't be in Graph Editor or it won't update) 590 | 591 | # Work around bug/feature causing programatically inserted keyframes to be snapped: 592 | if(snapChecked): 593 | snap.click() 594 | if(snapFrameChecked): 595 | snapFrame.click() 596 | 597 | if ReadFile(fileName): 598 | print 'Done.' 599 | else: 600 | print 'FAILED' 601 | 602 | finally: 603 | gc.collect() # Free memory, since we needed much of it 604 | 605 | checked = "" 606 | if(snapFrameChecked): 607 | #snapFrame.click() # if we unheck here it will still snap ... 608 | checked = "'Snap Frame'" 609 | if(snapChecked): 610 | #snap.click() # if we uncheck here it will still snap ... 611 | if 0 < len(checked): 612 | checked = " and " + checked 613 | checked = "'Snap'" + checked 614 | 615 | if 0 < len(checked): 616 | QtGui.QMessageBox.information( None, "Attention", "Had to un-push " + checked + " tool button in timeline! Push again (if wanted)." ) 617 | 618 | sfmApp.SetTimelineMode(oldTimelineMode) 619 | #sfm.SetOperationMode("Pass") 620 | 621 | ImportGameRecord() 622 | --------------------------------------------------------------------------------