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 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
30 'func=detail&aid=23564',
31 'category': 'Game Engine'}
40 def CopyPythonLibs(dst
, overwrite_lib
, report
=print):
43 # use python module to find pytohn's libpath
44 src
= os
.path
.dirname(platform
.__file
__)
46 # dst points to lib/, but src points to current python's library path, eg:
47 # '/usr/lib/python3.2' vs '/usr/lib'
48 # append python's library dir name to destination, so only python's
49 # libraries would be copied
50 if os
.name
== 'posix':
51 dst
= os
.path
.join(dst
, os
.path
.basename(src
))
53 if os
.path
.exists(src
):
55 if os
.path
.exists(dst
):
62 shutil
.copytree(src
, dst
, ignore
=lambda dir, contents
: [i
for i
in contents
if i
== '__pycache__'])
64 report({'WARNING'}, "Python not found in %r, skipping pythn copy" % src
)
67 def WriteAppleRuntime(player_path
, output_path
, copy_python
, overwrite_lib
):
68 # Enforce the extension
69 if not output_path
.endswith('.app'):
72 # Use the system's cp command to preserve some meta-data
73 os
.system('cp -R "%s" "%s"' % (player_path
, output_path
))
75 bpy
.ops
.wm
.save_as_mainfile(filepath
=os
.path
.join(output_path
, "Contents/Resources/game.blend"),
81 # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app
84 def WriteRuntime(player_path
, output_path
, copy_python
, overwrite_lib
, copy_dlls
, report
=print):
88 if not os
.path
.isfile(player_path
) and not(os
.path
.exists(player_path
) and player_path
.endswith('.app')):
89 report({'ERROR'}, "The player could not be found! Runtime not saved")
92 # Check if we're bundling a .app
93 if player_path
.endswith('.app'):
94 WriteAppleRuntime(player_path
, output_path
, copy_python
, overwrite_lib
)
97 # Enforce "exe" extension on Windows
98 if player_path
.endswith('.exe') and not output_path
.endswith('.exe'):
101 # Get the player's binary and the offset for the blend
102 file = open(player_path
, 'rb')
103 player_d
= file.read()
107 # Create a tmp blend file (Blenderplayer doesn't like compressed blends)
108 tempdir
= tempfile
.mkdtemp()
109 blend_path
= os
.path
.join(tempdir
, bpy
.path
.clean_name(output_path
))
110 bpy
.ops
.wm
.save_as_mainfile(filepath
=blend_path
,
111 relative_remap
=False,
115 blend_path
+= '.blend'
118 blend_file
= open(blend_path
, 'rb')
119 blend_d
= blend_file
.read()
122 # Get rid of the tmp blend, we're done with it
123 os
.remove(blend_path
)
126 # Create a new file for the bundled runtime
127 output
= open(output_path
, 'wb')
129 # Write the player and blend data to the new runtime
130 print("Writing runtime...", end
=" ")
131 output
.write(player_d
)
132 output
.write(blend_d
)
134 # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it)
135 output
.write(struct
.pack('B', (offset
>>24)&0xFF))
136 output
.write(struct
.pack('B', (offset
>>16)&0xFF))
137 output
.write(struct
.pack('B', (offset
>>8)&0xFF))
138 output
.write(struct
.pack('B', (offset
>>0)&0xFF))
140 # Stuff for the runtime
141 output
.write(b
'BRUNTIME')
146 # Make the runtime executable on Linux
147 if os
.name
== 'posix':
148 os
.chmod(output_path
, 0o755)
150 # Copy bundled Python
151 blender_dir
= os
.path
.dirname(bpy
.app
.binary_path
)
152 runtime_dir
= os
.path
.dirname(output_path
)
155 print("Copying Python files...", end
=" ")
156 py_folder
= os
.path
.join(bpy
.app
.version_string
.split()[0], "python", "lib")
157 dst
= os
.path
.join(runtime_dir
, py_folder
)
158 CopyPythonLibs(dst
, overwrite_lib
, report
)
163 print("Copying DLLs...", end
=" ")
164 for file in [i
for i
in os
.listdir(blender_dir
) if i
.lower().endswith('.dll')]:
165 src
= os
.path
.join(blender_dir
, file)
166 dst
= os
.path
.join(runtime_dir
, file)
167 shutil
.copy2(src
, dst
)
171 from bpy
.props
import *
174 class SaveAsRuntime(bpy
.types
.Operator
):
175 bl_idname
= "wm.save_as_runtime"
176 bl_label
= "Save As Game Engine Runtime"
177 bl_options
= {'REGISTER'}
179 if sys
.platform
== 'darwin':
180 # XXX, this line looks suspicious, could be done better?
181 blender_bin_dir
= '/' + os
.path
.join(*bpy
.app
.binary_path
.split('/')[0:-4])
184 blender_bin_path
= bpy
.app
.binary_path
185 blender_bin_dir
= os
.path
.dirname(blender_bin_path
)
186 ext
= os
.path
.splitext(blender_bin_path
)[-1].lower()
188 default_player_path
= os
.path
.join(blender_bin_dir
, 'blenderplayer' + ext
)
189 player_path
= StringProperty(
191 description
="The path to the player to use",
192 default
=default_player_path
,
195 filepath
= StringProperty(
198 copy_python
= BoolProperty(
200 description
="Copy bundle Python with the runtime",
203 overwrite_lib
= BoolProperty(
204 name
="Overwrite 'lib' folder",
205 description
="Overwrites the lib folder (if one exists) with the bundled Python lib folder",
209 # Only Windows has dlls to copy
211 copy_dlls
= BoolProperty(
213 description
="Copy all needed DLLs with the runtime",
219 def execute(self
, context
):
221 start_time
= time
.clock()
222 print("Saving runtime to %r" % self
.filepath
)
223 WriteRuntime(self
.player_path
,
230 print("Finished in %.4fs" % (time
.clock()-start_time
))
233 def invoke(self
, context
, event
):
234 if not self
.filepath
:
235 ext
= '.app' if sys
.platform
== 'darwin' else os
.path
.splitext(bpy
.app
.binary_path
)[-1]
236 self
.filepath
= bpy
.path
.ensure_ext(bpy
.data
.filepath
, ext
)
238 wm
= context
.window_manager
239 wm
.fileselect_add(self
)
240 return {'RUNNING_MODAL'}
243 def menu_func(self
, context
):
244 self
.layout
.operator(SaveAsRuntime
.bl_idname
)
248 bpy
.utils
.register_module(__name__
)
250 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
254 bpy
.utils
.unregister_module(__name__
)
256 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
259 if __name__
== "__main__":