1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
20 "name": "Save As Game Engine Runtime",
21 "author": "Mitchell Stokes (Moguri)",
23 "blender": (2, 61, 0),
24 "location": "File > Export",
25 "description": "Bundle a .blend file with the Blenderplayer",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Game_Engine/Save_As_Runtime",
29 "category": "Game Engine",
39 def CopyPythonLibs(dst
, overwrite_lib
, report
=print):
42 # use python module to find pytohn's libpath
43 src
= os
.path
.dirname(platform
.__file
__)
45 # dst points to lib/, but src points to current python's library path, eg:
46 # '/usr/lib/python3.2' vs '/usr/lib'
47 # append python's library dir name to destination, so only python's
48 # libraries would be copied
49 if os
.name
== 'posix':
50 dst
= os
.path
.join(dst
, os
.path
.basename(src
))
52 if os
.path
.exists(src
):
54 if os
.path
.exists(dst
):
61 shutil
.copytree(src
, dst
, ignore
=lambda dir, contents
: [i
for i
in contents
if i
== '__pycache__'])
63 report({'WARNING'}, "Python not found in %r, skipping pythn copy" % src
)
66 def WriteAppleRuntime(player_path
, output_path
, copy_python
, overwrite_lib
):
67 # Enforce the extension
68 if not output_path
.endswith('.app'):
71 # Use the system's cp command to preserve some meta-data
72 os
.system('cp -R "%s" "%s"' % (player_path
, output_path
))
74 bpy
.ops
.wm
.save_as_mainfile(filepath
=os
.path
.join(output_path
, "Contents/Resources/game.blend"),
80 # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app
83 def WriteRuntime(player_path
, output_path
, copy_python
, overwrite_lib
, copy_dlls
, report
=print):
87 if not os
.path
.isfile(player_path
) and not(os
.path
.exists(player_path
) and player_path
.endswith('.app')):
88 report({'ERROR'}, "The player could not be found! Runtime not saved")
91 # Check if we're bundling a .app
92 if player_path
.endswith('.app'):
93 WriteAppleRuntime(player_path
, output_path
, copy_python
, overwrite_lib
)
96 # Enforce "exe" extension on Windows
97 if player_path
.endswith('.exe') and not output_path
.endswith('.exe'):
100 # Get the player's binary and the offset for the blend
101 file = open(player_path
, 'rb')
102 player_d
= file.read()
106 # Create a tmp blend file (Blenderplayer doesn't like compressed blends)
107 tempdir
= tempfile
.mkdtemp()
108 blend_path
= os
.path
.join(tempdir
, bpy
.path
.clean_name(output_path
))
109 bpy
.ops
.wm
.save_as_mainfile(filepath
=blend_path
,
110 relative_remap
=False,
116 blend_file
= open(blend_path
, 'rb')
117 blend_d
= blend_file
.read()
120 # Get rid of the tmp blend, we're done with it
121 os
.remove(blend_path
)
124 # Create a new file for the bundled runtime
125 output
= open(output_path
, 'wb')
127 # Write the player and blend data to the new runtime
128 print("Writing runtime...", end
=" ")
129 output
.write(player_d
)
130 output
.write(blend_d
)
132 # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it)
133 output
.write(struct
.pack('B', (offset
>>24)&0xFF))
134 output
.write(struct
.pack('B', (offset
>>16)&0xFF))
135 output
.write(struct
.pack('B', (offset
>>8)&0xFF))
136 output
.write(struct
.pack('B', (offset
>>0)&0xFF))
138 # Stuff for the runtime
139 output
.write(b
'BRUNTIME')
144 # Make the runtime executable on Linux
145 if os
.name
== 'posix':
146 os
.chmod(output_path
, 0o755)
148 # Copy bundled Python
149 blender_dir
= os
.path
.dirname(bpy
.app
.binary_path
)
150 runtime_dir
= os
.path
.dirname(output_path
)
153 print("Copying Python files...", end
=" ")
154 py_folder
= os
.path
.join(bpy
.app
.version_string
.split()[0], "python", "lib")
155 dst
= os
.path
.join(runtime_dir
, py_folder
)
156 CopyPythonLibs(dst
, overwrite_lib
, report
)
161 print("Copying DLLs...", end
=" ")
162 for file in [i
for i
in os
.listdir(blender_dir
) if i
.lower().endswith('.dll')]:
163 src
= os
.path
.join(blender_dir
, file)
164 dst
= os
.path
.join(runtime_dir
, file)
165 shutil
.copy2(src
, dst
)
169 from bpy
.props
import *
172 class SaveAsRuntime(bpy
.types
.Operator
):
173 bl_idname
= "wm.save_as_runtime"
174 bl_label
= "Save As Game Engine Runtime"
175 bl_options
= {'REGISTER'}
177 if sys
.platform
== 'darwin':
178 # XXX, this line looks suspicious, could be done better?
179 blender_bin_dir
= '/' + os
.path
.join(*bpy
.app
.binary_path
.split('/')[0:-4])
182 blender_bin_path
= bpy
.app
.binary_path
183 blender_bin_dir
= os
.path
.dirname(blender_bin_path
)
184 ext
= os
.path
.splitext(blender_bin_path
)[-1].lower()
186 default_player_path
= os
.path
.join(blender_bin_dir
, 'blenderplayer' + ext
)
187 player_path
= StringProperty(
189 description
="The path to the player to use",
190 default
=default_player_path
,
193 filepath
= StringProperty(
196 copy_python
= BoolProperty(
198 description
="Copy bundle Python with the runtime",
201 overwrite_lib
= BoolProperty(
202 name
="Overwrite 'lib' folder",
203 description
="Overwrites the lib folder (if one exists) with the bundled Python lib folder",
207 # Only Windows has dlls to copy
209 copy_dlls
= BoolProperty(
211 description
="Copy all needed DLLs with the runtime",
217 def execute(self
, context
):
219 start_time
= time
.clock()
220 print("Saving runtime to %r" % self
.filepath
)
221 WriteRuntime(self
.player_path
,
228 print("Finished in %.4fs" % (time
.clock()-start_time
))
231 def invoke(self
, context
, event
):
232 if not self
.filepath
:
233 ext
= '.app' if sys
.platform
== 'darwin' else os
.path
.splitext(bpy
.app
.binary_path
)[-1]
234 self
.filepath
= bpy
.path
.ensure_ext(bpy
.data
.filepath
, ext
)
236 wm
= context
.window_manager
237 wm
.fileselect_add(self
)
238 return {'RUNNING_MODAL'}
241 def menu_func(self
, context
):
242 self
.layout
.operator(SaveAsRuntime
.bl_idname
)
246 bpy
.utils
.register_module(__name__
)
248 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
252 bpy
.utils
.unregister_module(__name__
)
254 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
257 if __name__
== "__main__":