├── LICENSE ├── README.md └── render_timeremapper.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | time-remapper 2 | ============= 3 | 4 | Blender addon that allows you to remap time (go into slow motion or backwards in time). See this [demo video](https://www.youtube.com/watch?v=qKtptQ0wPwg). 5 | 6 | 7 | Installation and Usage 8 | ---------------------- 9 | 10 | Instructions for installing and using this can be found [here](http://blender.stackexchange.com/a/7799/1112). 11 | -------------------------------------------------------------------------------- /render_timeremapper.py: -------------------------------------------------------------------------------- 1 | # BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License 5 | # as published by the Free Software Foundation; either version 2 6 | # of the License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software Foundation, 15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | # 17 | # ##### END GPL LICENSE BLOCK ##### 18 | 19 | bl_info = { 20 | "name": "Time Remapper", 21 | "author": "Garrett, hxxr", 22 | "version": (0, 2, 1), 23 | "blender": (2, 70, 0), 24 | "location": "Properties > Render > Render Panel", 25 | "description": "Time remaps whole scene according to an animatable " 26 | "speed factor", 27 | "warning": "beta", 28 | "category": "Render", 29 | "wiki_url": "", 30 | "tracker_url": "https://github.com/hxxr/time-remapper"} 31 | 32 | 33 | import bpy 34 | from os import path, remove 35 | import signal 36 | 37 | 38 | class OBJECT_OT_render_TR(bpy.types.Operator): 39 | bl_idname = "render.render_timeremapper" 40 | bl_label = "Render using Time Remapper" 41 | bl_description = "This renders out frames based on your time remapping" 42 | bl_register = True 43 | 44 | rendering = False # Whether or not we are currently rendering 45 | stop = False # Whether or not we are done rendering the entire animation 46 | _looper = None # Object to loop rendering, regular for loops don't work 47 | _index = -1 # Index of current frame 48 | anim_frame = 0 # Current true frame being rendered 49 | 50 | # this is only used during rendering frames 51 | abort_render = bpy.props.BoolProperty(default=False) 52 | 53 | def SIGINT_handler(self, signum, frame): 54 | """This signal handler will be called when user hits CTL+C 55 | while rendering""" 56 | self.abort_render = True 57 | 58 | def pre_render(self, unused): 59 | # Tell operator we are currently rendering when rendering starts 60 | self.rendering = True 61 | 62 | def post_render(self, unused): 63 | # Tell operator we've finished rendering when rendering stops 64 | self.rendering = False 65 | 66 | def stop_render(self, unused): 67 | # Tell operator rendering has been cancelled by the GUI 68 | self.stop = True 69 | 70 | def execute(self, context): 71 | 72 | scene = context.scene 73 | 74 | # ensure they are using Cycles 75 | if scene.render.engine != "CYCLES": 76 | raise RuntimeError("\n\nYou must be using Cycles Render " 77 | "for this script to work properly.\n") 78 | # ensure they haven't selected a movie format 79 | if scene.render.is_movie_format: 80 | file_format = scene.render.image_settings.file_format 81 | raise RuntimeError("\n\nCannot render movie file, " 82 | "you must select an image format" 83 | "\n(Current file format: {})" 84 | .format(file_format)) 85 | 86 | # Register listeners for the renderer 87 | bpy.app.handlers.render_pre.append(self.pre_render) 88 | bpy.app.handlers.render_post.append(self.post_render) 89 | bpy.app.handlers.render_cancel.append(self.stop_render) 90 | 91 | print("Getting list of frames to be rendered " 92 | "(should take <1 second...)") 93 | self.TR_frames = get_TR_frames(context) 94 | 95 | # total number of frames 96 | self.total_num_fr = len(self.TR_frames) 97 | 98 | print("\n\nRendering " + str(self.total_num_fr) + 99 | " frames now ... to stop after rendering current frame, " 100 | "press CTL+C...\n\n") 101 | 102 | # store original render path 103 | self.orig_render_path = scene.render.filepath 104 | 105 | # keep a count for labelling the filenames 106 | self.count = 0 107 | 108 | self.first_frame = scene.timeremap_startframe 109 | 110 | # before starting loop, set up a signal handler for CTL+C 111 | # since KeyboardInterrupt doesn't work while rendering 112 | # (see bit.ly/1cfBmlS) 113 | self.abort_render = False 114 | signal.signal(signal.SIGINT, self.SIGINT_handler) 115 | 116 | # start loop that renders the frames. 117 | # (anim_frame is the actual frame in animation we're at, ex: 4.5435) 118 | # for anim_frame in TR_frames: 119 | 120 | # Register a timer to run the function "modal" every few seconds 121 | self._looper = context.window_manager.event_timer_add( 122 | 2, context.window) 123 | context.window_manager.modal_handler_add(self) 124 | 125 | # Run the function "modal" if we are still rendering our animation 126 | return {"RUNNING_MODAL"} 127 | # end of execute(.) 128 | 129 | def modal(self, context, event): 130 | scene = context.scene 131 | 132 | if event.type == "TIMER" and self._index > len(self.TR_frames) - 2: 133 | self.stop = True 134 | 135 | if event.type == "TIMER" and self.stop: 136 | print("\nAborting Animation") 137 | 138 | # Remove handlers 139 | bpy.app.handlers.render_pre.remove(self.pre_render) 140 | bpy.app.handlers.render_post.remove(self.post_render) 141 | bpy.app.handlers.render_cancel.remove(self.stop_render) 142 | context.window_manager.event_timer_remove(self._looper) 143 | 144 | # reset the filepath in case user wants to play movie afterwards 145 | scene.render.filepath = self.orig_render_path 146 | 147 | # Reset TR rendering progress 148 | scene.timeremap_trueframe = "0" 149 | scene.timeremap_trframe = "0 / 0" 150 | 151 | print("\n\nDone") 152 | 153 | return {"FINISHED"} 154 | 155 | if event.type == "TIMER" and not self.rendering: 156 | if not self._index == -1: 157 | print("Frames completed: " + str(self.count + 1) + "/" + 158 | str(self.total_num_fr) + "\n\n") 159 | self.count += 1 160 | 161 | # Clean up after keyframing immune objects (if there are any) 162 | for iobj in self.immuneObjects: 163 | delete_locrot_keyframes(iobj, anim_frame) 164 | 165 | # Check if CTL+C was pressed by user 166 | if self.abort_render: 167 | # reset the SIGINT handler back to default 168 | signal.signal(signal.SIGINT, signal.default_int_handler) 169 | self.stop = True 170 | return {"PASS_THROUGH"} 171 | 172 | self._index += 1 173 | anim_frame = self.TR_frames[self._index] 174 | 175 | print("-------------------") 176 | 177 | # check for frame step and skip frame if necessary 178 | if self.count % scene.frame_step != 0: 179 | print("Stepping over frame " + 180 | str(self.first_frame + self.count) + 181 | ". (Frame Step is " + str(scene.frame_step) + ")") 182 | self.count += 1 183 | return {"PASS_THROUGH"} 184 | 185 | # render frame, ie. the number we assign this frame 186 | render_frame = self.first_frame + self.count 187 | 188 | # Update render status 189 | scene.timeremap_trueframe = ('%.3f' % anim_frame) 190 | if scene.timeremap_method == "TTC": 191 | scene.timeremap_trframe = str( 192 | render_frame) + " / " + str(self.total_num_fr + 193 | scene.timeremap_startframe - 1) 194 | else: 195 | scene.timeremap_trframe = str( 196 | self.count + 1) + " / " + str(self.total_num_fr) 197 | 198 | ################################# 199 | # Dealing with Immune Objects # 200 | ################################# 201 | 202 | # list of immune objects that will need to have their inserted 203 | # keyframes deleted 204 | self.immuneObjects = [] 205 | # Minimum frame offset. We don't want to have temporary keyframes 206 | # placed on integer frames (ex: 4.0, 7.0), since it could mess with 207 | # original keyframes. I found this value to work well empirically, 208 | # any smaller and Blender treats the keyframes (4.00 and 4.01) as 209 | # the same keyframe. 0.03 is not a perceptible change 210 | # (this is a hack and could break in future releases) 211 | min_fr_offset = 0.03 212 | if using_immune_objects(context): 213 | # check if anim_frame is too close to being integer frame 214 | # and if so, push away to have minimum offset 215 | if 0 <= anim_frame % 1 < min_fr_offset: 216 | anim_frame += min_fr_offset - anim_frame % 1 217 | elif 1 - min_fr_offset < anim_frame % 1 < 1: 218 | anim_frame -= anim_frame % 1 - (1 - min_fr_offset) 219 | 220 | for iobj_name in [scene.timeremap_immuneObject1, 221 | scene.timeremap_immuneObject2, 222 | scene.timeremap_immuneObject3]: 223 | # check if this property is set 224 | if iobj_name != "": 225 | # skip this object if the user has changed the name 226 | if iobj_name not in scene.objects.keys(): 227 | continue 228 | iobj = scene.objects[iobj_name] 229 | keyframe_locrot_by_target_frame(iobj, render_frame, 230 | anim_frame) 231 | self.immuneObjects.append(iobj) 232 | 233 | ######################################## 234 | # End of Dealing with Immune Objects # 235 | ######################################## 236 | 237 | # create filename. 238 | # Note that Blender expects a four digit integer at the end. 239 | current_renderpath = self.orig_render_path + \ 240 | str(render_frame).zfill(4) 241 | 242 | # check if file exists, and if so whether we should overwrite it 243 | # first we get the full current path to image to be rendered by 244 | # adding the extension if File Extensions is enabled. 245 | full_current_renderpath = bpy.path.abspath(current_renderpath) + \ 246 | scene.render.file_extension * \ 247 | (scene.render.use_file_extension is True) 248 | if path.exists(full_current_renderpath): 249 | if scene.render.use_overwrite is False: 250 | print("Skipping frame " + str(render_frame) + 251 | " because there already exists the file: " + 252 | full_current_renderpath) 253 | self.count += 1 254 | return {"PASS_THROUGH"} 255 | else: 256 | print("File: " + full_current_renderpath + 257 | " will be overwritten.") 258 | # Wait to overwrite it until last possible moment. 259 | 260 | # check if we need Placeholders 261 | if scene.render.use_placeholder is True: 262 | # delete the old file if it exists 263 | if path.exists(full_current_renderpath): 264 | remove(full_current_renderpath) 265 | # create placeholder (tag 'a' helps prevent race errors) 266 | open(full_current_renderpath, "a").close() 267 | 268 | # Jump to animation frame (frame is a float) 269 | scene.frame_set(int(anim_frame), anim_frame % 1) 270 | 271 | scene.render.filepath = current_renderpath 272 | print("Rendering true frame:", anim_frame) 273 | # Render frame 274 | bpy.ops.render.render("INVOKE_DEFAULT", write_still=True) 275 | 276 | return {"PASS_THROUGH"} 277 | # End loop that renders frames 278 | # end of class OBJECT_OT_render_TR 279 | 280 | 281 | class OBJECT_OT_playback_TR(bpy.types.Operator): 282 | bl_idname = "render.playback_timeremapper" 283 | bl_label = "Playback time remapped frames" 284 | bl_description = "Plays back frames, defining the start and end" \ 285 | " based on the time remapping" 286 | bl_register = True 287 | 288 | def execute(self, context): 289 | 290 | scene = context.scene 291 | 292 | print("Preparing playback") 293 | # get number of frames that we need to play back 294 | num_frames = len(get_TR_frames(context)) 295 | 296 | old_frame_end = scene.timeremap_endframe 297 | 298 | scene.timeremap_endframe = scene.timeremap_startframe + num_frames - 1 299 | 300 | bpy.ops.render.play_rendered_anim() 301 | 302 | # restore the old end frame 303 | scene.timeremap_endframe = old_frame_end 304 | 305 | return {"FINISHED"} 306 | 307 | # end of class OBJECT_OT_playback_TR 308 | 309 | 310 | def draw(self, context): 311 | layout = self.layout 312 | 313 | scene = context.scene 314 | 315 | layout.label("Time Remapper:") 316 | 317 | row = layout.row(align=True) 318 | row.prop(scene, "timeremap_method") 319 | if scene.timeremap_method == "SF": 320 | row.prop(scene, "timeremap_speedfactor") 321 | elif scene.timeremap_method == "TTC": 322 | row.prop(scene, "timeremap_TTC") 323 | 324 | row = layout.row(align=True) 325 | rowsub = row.row(align=True) 326 | rowsub.operator("render.render_timeremapper", 327 | text="TR Animation", 328 | icon="RENDER_ANIMATION") 329 | rowsub.operator("render.playback_timeremapper", 330 | text="TR Playback", 331 | icon="PLAY") 332 | 333 | row = layout.row(align=True) 334 | rowsub = row.row(align=True) 335 | rowsub.label("Immune object 1:") 336 | rowsub.prop_search(scene, "timeremap_immuneObject1", scene, "objects", 337 | text="") 338 | row = layout.row(align=True) 339 | rowsub = row.row(align=True) 340 | rowsub.label("Immune object 2:") 341 | rowsub.prop_search(scene, "timeremap_immuneObject2", scene, "objects", 342 | text="") 343 | row = layout.row(align=True) 344 | rowsub = row.row(align=True) 345 | rowsub.label("Immune object 3:") 346 | rowsub.prop_search(scene, "timeremap_immuneObject3", scene, "objects", 347 | text="") 348 | 349 | row = layout.row(align=True) 350 | rowsub = row.row(align=True) 351 | rowsub.prop(scene, "timeremap_startframe") 352 | rowsub.prop(scene, "timeremap_endframe") 353 | 354 | row = layout.row(align=True) 355 | row.label("TR Render Progress:") 356 | 357 | row = layout.row(align=True) 358 | rowsub = row.row(align=True) 359 | rowsub.label("True Frame:") 360 | rowsub.prop(scene, "timeremap_trueframe") 361 | 362 | row = layout.row(align=True) 363 | rowsub = row.row(align=True) 364 | rowsub.label("Remapped Frame:") 365 | rowsub.prop(scene, "timeremap_trframe") 366 | 367 | 368 | def find_fcurve(scene_or_obj, data_path, index=0): 369 | """Find a particular F-Curve.""" 370 | anim_data = scene_or_obj.animation_data 371 | for fcurve in anim_data.action.fcurves: 372 | if fcurve.data_path == data_path and fcurve.array_index == index: 373 | return fcurve 374 | 375 | 376 | def is_keyframed(scene_or_obj, data_path, index=0): 377 | """Check if the scene or object property is already keyframed 378 | Ideas from @CoDEmanX on Blender SE for this.""" 379 | 380 | anim = scene_or_obj.animation_data 381 | 382 | if anim is not None and anim.action is not None: 383 | for fcurve in anim.action.fcurves: 384 | if fcurve.data_path == data_path and fcurve.array_index == index: 385 | return True 386 | return False 387 | 388 | 389 | def using_immune_objects(context): 390 | """Check if the user has a valid immune object set, i.e. an object 391 | that will be immune to time remapping.""" 392 | scene = context.scene 393 | 394 | for iobj_name in [scene.timeremap_immuneObject1, 395 | scene.timeremap_immuneObject2, 396 | scene.timeremap_immuneObject3]: 397 | if iobj_name != "": 398 | if iobj_name not in scene.objects.keys(): 399 | print("WARNING: One of your selected immune objects ('" + 400 | iobj_name + "') no longer corresponds to an object. " 401 | "Did you change the name of that object?") 402 | continue 403 | else: 404 | return True 405 | 406 | return False 407 | 408 | 409 | def keyframe_locrot_by_target_frame(obj, target_fr, frame): 410 | """Set the location and rotation of the object at frame to be same as 411 | values at target keyframe 412 | 413 | Return: if the object already had a keyframe there, then return a list of 414 | 3-tuples, one 3-tuple for each keyframed property""" 415 | 416 | for data_path, tot_indices in [("location", 3), ("rotation_euler", 3), 417 | ("rotation_axis_angle", 4), 418 | ("rotation_quaternion", 4)]: 419 | # loop through index 420 | # (we loop 4 times for properties like Quaternion Rot.) 421 | for ind in range(tot_indices): 422 | if is_keyframed(obj, data_path=data_path, index=ind): 423 | fcurve = find_fcurve(obj, data_path, index=ind) 424 | else: 425 | continue 426 | 427 | # update the property to have value that it has at target frame 428 | props_to_update = getattr(obj, data_path) 429 | props_to_update[ind] = fcurve.evaluate(target_fr) 430 | setattr(obj, data_path, props_to_update) 431 | 432 | obj.keyframe_insert(data_path=data_path, index=ind, frame=frame) 433 | 434 | return 435 | 436 | 437 | def delete_locrot_keyframes(obj, frame): 438 | """Delete all the location and rotation keyframes for object at given 439 | frame.""" 440 | 441 | for data_path, tot_indices in [("location", 3), ("rotation_euler", 3), 442 | ("rotation_axis_angle", 4), 443 | ("rotation_quaternion", 4)]: 444 | # loop through index 445 | # (we loop 4 times for properties like Quaternion Rot.) 446 | for ind in range(tot_indices): 447 | if is_keyframed(obj, data_path=data_path, index=ind) is False: 448 | continue 449 | obj.keyframe_delete(data_path, ind, frame) 450 | 451 | return 452 | 453 | 454 | def get_TR_frames(context): 455 | """Gets a list of time-remapped frames to be rendered""" 456 | 457 | scene = context.scene 458 | 459 | if scene.timeremap_method == "SF": 460 | TR_frames = get_TR_frames_from_SF(context) 461 | elif scene.timeremap_method == "TTC": 462 | TR_frames = get_TR_frames_from_TTC(context) 463 | else: 464 | assert False 465 | 466 | return TR_frames 467 | 468 | 469 | def get_TR_frames_from_SF(context): 470 | """Gets a list of time-remapped frames to be rendered by 471 | looking at the Speed Factor parameter.""" 472 | 473 | scene = context.scene 474 | 475 | # Time-remapped frames to render 476 | TR_frames = [] 477 | 478 | # current time-remapped frame 479 | current_TR_frame = scene.timeremap_startframe 480 | 481 | # for unkeyframed, we can't use the F-Curve, so we treat it separately 482 | if is_keyframed(scene, "timeremap_speedfactor") is False: 483 | 484 | # avoid infinite loop by checing that speed factor's positive 485 | if scene.timeremap_speedfactor <= 0: 486 | raise RuntimeError("\n\nYour speed factor must always be" 487 | "positive to avoid getting stuck in an" 488 | " infinite loop!") 489 | 490 | # loop through all frames until the end 491 | while current_TR_frame <= scene.timeremap_endframe: 492 | TR_frames.append(current_TR_frame) 493 | current_TR_frame += scene.timeremap_speedfactor 494 | 495 | return TR_frames 496 | # end of if block 497 | 498 | # speed factor's F-Curve 499 | SF_fcurve = find_fcurve(scene, "timeremap_speedfactor") 500 | assert SF_fcurve is not None 501 | 502 | # we loop through however many (time-remapped) frames it takes 503 | # to get to the end frame 504 | while current_TR_frame <= scene.timeremap_endframe: 505 | 506 | # add current frame to our list 507 | TR_frames.append(current_TR_frame) 508 | 509 | # get current speed factor 510 | current_SF = SF_fcurve.evaluate(current_TR_frame) 511 | 512 | # avoid infinite loop by checing that speed factor's positive 513 | if current_SF <= 0: 514 | raise RuntimeError("\n\nYour speed factor must always be" 515 | "positive to avoid getting stuck in an" 516 | " infinite loop!") 517 | 518 | # move to next frame based on the current value of the speed factor 519 | current_TR_frame += current_SF 520 | # end while loop 521 | 522 | return TR_frames 523 | # end of get_TR_frames_from_SF(.) 524 | 525 | 526 | def get_TR_frames_from_TTC(context): 527 | """Gets a list of time-remapped frames to be rendered by 528 | looking at the Time Time Curve parameter.""" 529 | 530 | scene = context.scene 531 | 532 | if is_keyframed(scene, "timeremap_TTC") is False: 533 | raise RuntimeError("the property timeremap_TTC must be keyframed" 534 | "\nIt cannot be a constant value.") 535 | 536 | # Time-remapped frames to render 537 | TR_frames = [] 538 | # jump to non-time-remapped start frame 539 | nonTR_frame = scene.timeremap_startframe 540 | scene.frame_set(nonTR_frame) 541 | 542 | # to avoid getting stuck in an infinite loop (ex: TT curve never reaches 543 | # the end frame), we break when we exceed max_frames. 544 | count = 0 545 | max_frames = 100000 546 | 547 | # TTC's F-Curve 548 | TTC_fcurve = find_fcurve(scene, "timeremap_TTC") 549 | assert TTC_fcurve is not None 550 | 551 | current_TTC_value = TTC_fcurve.evaluate(nonTR_frame) 552 | 553 | # we loop through however many (time-remapped) frames 554 | # it takes to get to the end frame 555 | while current_TTC_value <= scene.timeremap_endframe: 556 | 557 | TR_frames.append(current_TTC_value) 558 | 559 | nonTR_frame += 1 560 | current_TTC_value = TTC_fcurve.evaluate(nonTR_frame) 561 | 562 | count += 1 563 | if count >= max_frames: 564 | raise RuntimeError("\n\nHaven't reached end after counting 100 000" 565 | "frames!\nMake sure the TT Curve value reaches" 566 | " the end frame at some point.") 567 | # end of while loop 568 | 569 | return TR_frames 570 | # end of get_TR_frames_from_TTC(.) 571 | 572 | 573 | def update_TR_method(self, context): 574 | """Check if user switched to using a TT curve. If so, and it is not 575 | keyframed, then keyframe it to produce a 45 degree angle curve (which 576 | corresponds to a one-to-one time mapping) 577 | 578 | Ideas from @pinkvertex on Blender SE.""" 579 | 580 | scene = context.scene 581 | 582 | print("Inside update method") 583 | 584 | # if not switching to TT curve, or if it's already keyframed, return 585 | if scene.timeremap_method != "TTC" or is_keyframed(scene, "timeremap_TTC"): 586 | print("OK no need") 587 | return 588 | 589 | if not scene.animation_data: 590 | scene.animation_data_create() 591 | if not scene.animation_data.action: 592 | scene.animation_data.action = (bpy.data.actions 593 | .new("timeremap_TTC_action")) 594 | 595 | # find the correct f-curve in case there's multiple 596 | fcurve = scene.animation_data.action.fcurves.new("timeremap_TTC") 597 | # keyframe it to make a 45 degree straight line 598 | fcurve.extrapolation = "LINEAR" 599 | fcurve.keyframe_points.insert(frame=0.0, value=0.0) 600 | fcurve.keyframe_points.insert(frame=1.0, value=1.0) 601 | 602 | 603 | def register(): 604 | bpy.utils.register_module(__name__) 605 | 606 | bpy.types.Scene.timeremap_speedfactor = bpy.props.FloatProperty( 607 | name="Speed factor", 608 | options={"ANIMATABLE"}, 609 | default=1.0 610 | ) 611 | bpy.types.Scene.timeremap_TTC = bpy.props.FloatProperty( 612 | name="TT Curve", 613 | options={"ANIMATABLE"}, 614 | default=0.0 615 | ) 616 | bpy.types.Scene.timeremap_method = bpy.props.EnumProperty( 617 | name="", 618 | description="Method for defining the time remapping", 619 | items=[ 620 | ("SF", "Speed Factor", "Use a speed factor " 621 | "(where 0.5 means 2x slow-mo)"), 622 | ("TTC", "Time-Time Curve", 623 | "Use a curve which shows how rendered frame maps" 624 | "to true-time frame") 625 | ], 626 | default="SF", update=update_TR_method 627 | ) 628 | 629 | # We allow for 3 immune objects (of course, this could be increased) 630 | # I couldn't figure out how to get these into a collection and still use 631 | # the prop_search funciton while drawing panel. 632 | bpy.types.Scene.timeremap_immuneObject1 = bpy.props.StringProperty( 633 | name="Immune Object 1", 634 | description="Make object immune to time remapping effects" 635 | ) 636 | bpy.types.Scene.timeremap_immuneObject2 = bpy.props.StringProperty( 637 | name="Immune Object 2", 638 | description="Make object immune to time remapping effects" 639 | ) 640 | bpy.types.Scene.timeremap_immuneObject3 = bpy.props.StringProperty( 641 | name="Immune Object 3", 642 | description="Make object immune to time remapping effects" 643 | ) 644 | 645 | bpy.types.Scene.timeremap_startframe = bpy.props.IntProperty( 646 | name="Start", 647 | description="Remapped frame to start TR rendering at", 648 | min=0, 649 | default=1) 650 | 651 | bpy.types.Scene.timeremap_endframe = bpy.props.IntProperty( 652 | name="End", 653 | description="True frame to end TR rendering at", 654 | min=1, 655 | default=250) 656 | 657 | bpy.types.Scene.timeremap_trueframe = bpy.props.StringProperty( 658 | name="", 659 | description="True frame currently being rendered", 660 | default="0" 661 | ) 662 | 663 | bpy.types.Scene.timeremap_trframe = bpy.props.StringProperty( 664 | name="", 665 | description="Remapped frame currently being rendered", 666 | default="0 / 0" 667 | ) 668 | 669 | # Draw panel under the header "Render" in Render tab of Properties window 670 | bpy.types.RENDER_PT_render.append(draw) 671 | 672 | 673 | def unregister(): 674 | 675 | del bpy.types.Scene.timeremap_speedfactor 676 | del bpy.types.Scene.timeremap_TTC 677 | del bpy.types.Scene.timeremap_method 678 | del bpy.types.Scene.timeremap_immuneObject1 679 | del bpy.types.Scene.timeremap_immuneObject2 680 | del bpy.types.Scene.timeremap_immuneObject3 681 | del bpy.types.Scene.timeremap_startframe 682 | del bpy.types.Scene.timeremap_endframe 683 | del bpy.types.Scene.timeremap_trueframe 684 | del bpy.types.Scene.timeremap_trframe 685 | 686 | # remove the panel from the UI 687 | bpy.types.RENDER_PT_render.remove(draw) 688 | 689 | bpy.utils.unregister_module(__name__) 690 | 691 | 692 | if __name__ == "__main__": 693 | register() 694 | --------------------------------------------------------------------------------