Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / game_engine_save_as_runtime.py
blob25e47d949d554b48661ed74f69bbc6370dc0903d
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 #####
19 bl_info = {
20 "name": "Save As Game Engine Runtime",
21 "author": "Mitchell Stokes (Moguri)",
22 "version": (0, 3, 1),
23 "blender": (2, 61, 0),
24 "location": "File > Export",
25 "description": "Bundle a .blend file with the Blenderplayer",
26 "warning": "",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Game_Engine/Save_As_Runtime",
29 "category": "Game Engine",
32 import bpy
33 import os
34 import sys
35 import shutil
36 import tempfile
39 def CopyPythonLibs(dst, overwrite_lib, report=print):
40 import platform
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):
53 write = False
54 if os.path.exists(dst):
55 if overwrite_lib:
56 shutil.rmtree(dst)
57 write = True
58 else:
59 write = True
60 if write:
61 shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i == '__pycache__'])
62 else:
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'):
69 output_path += '.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"),
75 relative_remap=False,
76 compress=False,
77 copy=True,
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):
84 import struct
86 # Check the paths
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")
89 return
91 # Check if we're bundling a .app
92 if player_path.endswith('.app'):
93 WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib)
94 return
96 # Enforce "exe" extension on Windows
97 if player_path.endswith('.exe') and not output_path.endswith('.exe'):
98 output_path += '.exe'
100 # Get the player's binary and the offset for the blend
101 file = open(player_path, 'rb')
102 player_d = file.read()
103 offset = file.tell()
104 file.close()
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,
111 compress=False,
112 copy=True,
115 # Get the blend data
116 blend_file = open(blend_path, 'rb')
117 blend_d = blend_file.read()
118 blend_file.close()
120 # Get rid of the tmp blend, we're done with it
121 os.remove(blend_path)
122 os.rmdir(tempdir)
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')
140 output.close()
142 print("done")
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)
152 if copy_python:
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)
157 print("done")
159 # And DLLs
160 if copy_dlls:
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)
167 print("done")
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])
180 ext = '.app'
181 else:
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(
188 name="Player Path",
189 description="The path to the player to use",
190 default=default_player_path,
191 subtype='FILE_PATH',
193 filepath = StringProperty(
194 subtype='FILE_PATH',
196 copy_python = BoolProperty(
197 name="Copy Python",
198 description="Copy bundle Python with the runtime",
199 default=True,
201 overwrite_lib = BoolProperty(
202 name="Overwrite 'lib' folder",
203 description="Overwrites the lib folder (if one exists) with the bundled Python lib folder",
204 default=False,
207 # Only Windows has dlls to copy
208 if ext == '.exe':
209 copy_dlls = BoolProperty(
210 name="Copy DLLs",
211 description="Copy all needed DLLs with the runtime",
212 default=True,
214 else:
215 copy_dlls = False
217 def execute(self, context):
218 import time
219 start_time = time.clock()
220 print("Saving runtime to %r" % self.filepath)
221 WriteRuntime(self.player_path,
222 self.filepath,
223 self.copy_python,
224 self.overwrite_lib,
225 self.copy_dlls,
226 self.report,
228 print("Finished in %.4fs" % (time.clock()-start_time))
229 return {'FINISHED'}
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)
245 def register():
246 bpy.utils.register_module(__name__)
248 bpy.types.INFO_MT_file_export.append(menu_func)
251 def unregister():
252 bpy.utils.unregister_module(__name__)
254 bpy.types.INFO_MT_file_export.remove(menu_func)
257 if __name__ == "__main__":
258 register()