1142 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1142 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import errno
 | |
| import glob
 | |
| import json
 | |
| import os
 | |
| from queue import Queue
 | |
| import re
 | |
| import shlex
 | |
| import shutil
 | |
| import stat
 | |
| from string import Template
 | |
| import subprocess
 | |
| import threading
 | |
| import time
 | |
| import traceback
 | |
| from typing import Callable
 | |
| import webbrowser
 | |
| 
 | |
| import bpy
 | |
| 
 | |
| from lnx import assets
 | |
| from lnx.exporter import LeenkxExporter
 | |
| import lnx.lib.make_datas
 | |
| import lnx.lib.server
 | |
| import lnx.live_patch as live_patch
 | |
| import lnx.log as log
 | |
| import lnx.make_logic as make_logic
 | |
| import lnx.make_renderpath as make_renderpath
 | |
| import lnx.make_state as state
 | |
| import lnx.make_world as make_world
 | |
| import lnx.utils
 | |
| import lnx.utils_vs
 | |
| import lnx.write_data as write_data
 | |
| 
 | |
| if lnx.is_reload(__name__):
 | |
|     assets = lnx.reload_module(assets)
 | |
|     lnx.exporter = lnx.reload_module(lnx.exporter)
 | |
|     from lnx.exporter import LeenkxExporter
 | |
|     lnx.lib.make_datas = lnx.reload_module(lnx.lib.make_datas)
 | |
|     lnx.lib.server = lnx.reload_module(lnx.lib.server)
 | |
|     live_patch = lnx.reload_module(live_patch)
 | |
|     log = lnx.reload_module(log)
 | |
|     make_logic = lnx.reload_module(make_logic)
 | |
|     make_renderpath = lnx.reload_module(make_renderpath)
 | |
|     state = lnx.reload_module(state)
 | |
|     make_world = lnx.reload_module(make_world)
 | |
|     lnx.utils = lnx.reload_module(lnx.utils)
 | |
|     lnx.utils_vs = lnx.reload_module(lnx.utils_vs)
 | |
|     write_data = lnx.reload_module(write_data)
 | |
| else:
 | |
|     lnx.enable_reload(__name__)
 | |
| 
 | |
| scripts_mtime = 0 # Monitor source changes
 | |
| profile_time = 0
 | |
| 
 | |
| # Queue of threads and their done callbacks. Item format: [thread, done]
 | |
| thread_callback_queue = Queue(maxsize=0)
 | |
| 
 | |
| 
 | |
| def run_proc(cmd, done: Callable) -> subprocess.Popen:
 | |
|     """Creates a subprocess with the given command and returns it.
 | |
| 
 | |
|     If Blender is not running in background mode, a thread is spawned
 | |
|     that waits until the subprocess has finished executing to not freeze
 | |
|     the UI, otherwise (in background mode) execution is blocked until
 | |
|     the subprocess has finished.
 | |
| 
 | |
|     If `done` is not `None`, it is called afterwards in the main thread.
 | |
|     """
 | |
|     use_thread = not bpy.app.background
 | |
| 
 | |
|     def wait_for_proc(proc: subprocess.Popen):
 | |
|         proc.wait()
 | |
| 
 | |
|         if use_thread:
 | |
|             # Put the done callback into the callback queue so that it
 | |
|             # can be received by a polling function in the main thread
 | |
|             thread_callback_queue.put([threading.current_thread(), done], block=True)
 | |
|             while not thread_callback_queue.empty():
 | |
|                 thread, callback = thread_callback_queue.get()
 | |
|                 if callback is not None:
 | |
|                     callback()
 | |
|         else:
 | |
|             done()
 | |
| 
 | |
|     print(*cmd)
 | |
|     p = subprocess.Popen(cmd)
 | |
| 
 | |
|     if use_thread:
 | |
|         threading.Thread(target=wait_for_proc, args=(p,)).start()
 | |
|     else:
 | |
|         wait_for_proc(p)
 | |
| 
 | |
|     return p
 | |
| 
 | |
| 
 | |
| def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants):
 | |
|     os.chdir(raw_shaders_path + '/' + shader_name)
 | |
| 
 | |
|     # Open json file
 | |
|     json_name = shader_name + '.json'
 | |
|     with open(json_name, encoding='utf-8') as f:
 | |
|         json_file = f.read()
 | |
|     json_data = json.loads(json_file)
 | |
| 
 | |
|     fp = lnx.utils.get_fp_build()
 | |
|     lnx.lib.make_datas.make(res, shader_name, json_data, fp, defs, make_variants)
 | |
| 
 | |
|     path = fp + '/compiled/Shaders'
 | |
|     contexts = json_data['contexts']
 | |
|     for ctx in contexts:
 | |
|         for s in ['vertex_shader', 'fragment_shader', 'geometry_shader', 'tesscontrol_shader', 'tesseval_shader']:
 | |
|             if s in ctx:
 | |
|                 shutil.copy(ctx[s], path + '/' + ctx[s].split('/')[-1])
 | |
| 
 | |
| def remove_readonly(func, path, excinfo):
 | |
|     os.chmod(path, stat.S_IWRITE)
 | |
|     func(path)
 | |
| 
 | |
| def export_data(fp, sdk_path):
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
|     rpdat = lnx.utils.get_rp()
 | |
| 
 | |
|     if wrd.lnx_verbose_output:
 | |
|         print(f'Leenkx v{wrd.lnx_version} ({wrd.lnx_commit})')
 | |
|         print(f'Blender: {bpy.app.version_string}, Target: {state.target}, GAPI: {lnx.utils.get_gapi()}')
 | |
| 
 | |
|     # Clean compiled variants if cache is disabled
 | |
|     build_dir = lnx.utils.get_fp_build()
 | |
|     if not wrd.lnx_cache_build:
 | |
|         if os.path.isdir(build_dir + '/debug/html5-resources'):
 | |
|             shutil.rmtree(build_dir + '/debug/html5-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/krom-resources'):
 | |
|             shutil.rmtree(build_dir + '/krom-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/debug/krom-resources'):
 | |
|             shutil.rmtree(build_dir + '/debug/krom-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/windows-resources'):
 | |
|             shutil.rmtree(build_dir + '/windows-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/linux-resources'):
 | |
|             shutil.rmtree(build_dir + '/linux-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/osx-resources'):
 | |
|             shutil.rmtree(build_dir + '/osx-resources', onerror=remove_readonly)
 | |
|         if os.path.isdir(build_dir + '/compiled/Shaders'):
 | |
|             shutil.rmtree(build_dir + '/compiled/Shaders', onerror=remove_readonly)
 | |
| 
 | |
|     raw_shaders_path = sdk_path + '/leenkx/Shaders/'
 | |
|     assets_path = sdk_path + '/leenkx/Assets/'
 | |
|     export_physics = bpy.data.worlds['Lnx'].lnx_physics != 'Disabled'
 | |
|     export_navigation = bpy.data.worlds['Lnx'].lnx_navigation != 'Disabled'
 | |
|     export_ui = bpy.data.worlds['Lnx'].lnx_ui != 'Disabled'
 | |
|     export_network = bpy.data.worlds['Lnx'].lnx_network != 'Disabled'
 | |
| 
 | |
|     assets.reset()
 | |
| 
 | |
|     # Build node trees
 | |
|     LeenkxExporter.import_traits = []
 | |
|     make_logic.build()
 | |
|     make_world.build()
 | |
|     make_renderpath.build()
 | |
| 
 | |
|     # Export scene data
 | |
|     assets.embedded_data = sorted(list(set(assets.embedded_data)))
 | |
|     physics_found = False
 | |
|     navigation_found = False
 | |
|     ui_found = False
 | |
|     network_found = False
 | |
|     LeenkxExporter.compress_enabled = state.is_publish and wrd.lnx_asset_compression
 | |
|     LeenkxExporter.optimize_enabled = state.is_publish and wrd.lnx_optimize_data
 | |
|     if not os.path.exists(build_dir + '/compiled/Assets'):
 | |
|         os.makedirs(build_dir + '/compiled/Assets')
 | |
| 
 | |
|     # Make all 'MESH' and 'EMPTY' objects visible to the depsgraph (we pass
 | |
|     # this to the exporter further below) with a temporary "zoo" collection
 | |
|     # in the current scene. We do this to ensure that (among other things)
 | |
|     # modifiers are applied to all exported objects.
 | |
|     export_coll = bpy.data.collections.new("export_coll")
 | |
|     bpy.context.scene.collection.children.link(export_coll)
 | |
|     export_coll_names = set(export_coll.all_objects.keys())
 | |
|     for scene in bpy.data.scenes:
 | |
|         if scene == bpy.context.scene:
 | |
|             continue
 | |
|         for o in scene.collection.all_objects:
 | |
|             if o.type in ('MESH', 'EMPTY'):
 | |
|                 if o.name not in export_coll_names:
 | |
|                     export_coll.objects.link(o)
 | |
|                     export_coll_names.add(o.name)
 | |
|     depsgraph = bpy.context.evaluated_depsgraph_get()
 | |
|     bpy.data.collections.remove(export_coll)  # Destroy the "zoo" collection
 | |
| 
 | |
|     for scene in bpy.data.scenes:
 | |
|         if scene.lnx_export:
 | |
|             ext = '.lz4' if LeenkxExporter.compress_enabled else '.lnx'
 | |
|             asset_path = build_dir + '/compiled/Assets/' + lnx.utils.safestr(scene.name) + ext
 | |
|             LeenkxExporter.export_scene(bpy.context, asset_path, scene=scene, depsgraph=depsgraph)
 | |
|             if LeenkxExporter.export_physics:
 | |
|                 physics_found = True
 | |
|             if LeenkxExporter.export_navigation:
 | |
|                 navigation_found = True
 | |
|             if LeenkxExporter.export_ui:
 | |
|                 ui_found = True
 | |
|             if LeenkxExporter.export_network:
 | |
|                 network_found = True
 | |
|             assets.add(asset_path)
 | |
| 
 | |
|     if physics_found is False: # Disable physics if no rigid body is exported
 | |
|         export_physics = False
 | |
| 
 | |
|     if navigation_found is False:
 | |
|         export_navigation = False
 | |
| 
 | |
|     if ui_found is False:
 | |
|         export_ui = False
 | |
| 
 | |
|     if network_found == False:
 | |
|         export_network = False
 | |
| 
 | |
|     # Ugly workaround: some logic nodes require Zui code even if no UI is used,
 | |
|     # for now enable UI export unless explicitly disabled.
 | |
|     export_ui = True
 | |
|     if wrd.lnx_ui == 'Disabled':
 | |
|         export_ui = False
 | |
| 
 | |
|     if wrd.lnx_network == 'Enabled':
 | |
|         export_network = True
 | |
| 
 | |
|     modules = []
 | |
|     if wrd.lnx_audio == 'Enabled':
 | |
|         modules.append('audio')
 | |
|     if export_physics:
 | |
|         modules.append('physics')
 | |
|     if export_navigation:
 | |
|         modules.append('navigation')
 | |
|     if export_ui:
 | |
|         modules.append('ui')
 | |
|     if export_network:
 | |
|         modules.append('network')
 | |
| 
 | |
|     defs = lnx.utils.def_strings_to_array(wrd.world_defs)
 | |
|     cdefs = lnx.utils.def_strings_to_array(wrd.compo_defs)
 | |
| 
 | |
|     if wrd.lnx_verbose_output:
 | |
|         log.info('Exported modules: '+', '.join(modules))
 | |
|         log.info('Shader flags: '+', '.join(defs))
 | |
|         log.info('Compositor flags: '+', '.join(cdefs))
 | |
|         log.info('Khafile flags: '+', '.join(assets.khafile_defs))
 | |
| 
 | |
|     # Render path is configurable at runtime
 | |
|     has_config = wrd.lnx_write_config or os.path.exists(lnx.utils.get_fp() + '/Bundled/config.lnx')
 | |
| 
 | |
|     # Write compiled.inc
 | |
|     shaders_path = build_dir + '/compiled/Shaders'
 | |
|     if not os.path.exists(shaders_path):
 | |
|         os.makedirs(shaders_path)
 | |
|     write_data.write_compiledglsl(defs + cdefs, make_variants=has_config)
 | |
| 
 | |
|     # Write referenced shader passes
 | |
|     if not os.path.isfile(build_dir + '/compiled/Shaders/shader_datas.lnx') or state.last_world_defs != wrd.world_defs:
 | |
|         res = {'shader_datas': []}
 | |
| 
 | |
|         for ref in assets.shader_passes:
 | |
|             # Ensure shader pass source exists
 | |
|             if not os.path.exists(raw_shaders_path + '/' + ref):
 | |
|                 continue
 | |
|             assets.shader_passes_assets[ref] = []
 | |
|             compile_shader_pass(res, raw_shaders_path, ref, defs + cdefs, make_variants=has_config)
 | |
| 
 | |
|         # Workaround to also export non-material world shaders
 | |
|         res['shader_datas'] += make_world.shader_datas
 | |
| 
 | |
|         if rpdat.lnx_lens or rpdat.lnx_lut:
 | |
|             for shader_pass in res["shader_datas"]:
 | |
|                 for context in shader_pass["contexts"]:
 | |
|                     for texture_unit in context["texture_units"]:
 | |
|                         # Lens Texture
 | |
|                         if rpdat.lnx_lens_texture != '' and rpdat.lnx_lens_texture != 'lenstexture.jpg' and "link" in texture_unit and texture_unit["link"] == "$lenstexture.jpg":
 | |
|                             texture_unit["link"] = f"${rpdat.lnx_lens_texture}"
 | |
|                         # LUT Colorgrading
 | |
|                         if rpdat.lnx_lut_texture != '' and rpdat.lnx_lut_texture != 'luttexture.jpg' and "link" in texture_unit and texture_unit["link"] == "$luttexture.jpg":
 | |
|                             texture_unit["link"] = f"${rpdat.lnx_lut_texture}"
 | |
| 
 | |
|         lnx.utils.write_lnx(shaders_path + '/shader_datas.lnx', res)
 | |
| 
 | |
|     if wrd.lnx_debug_console and rpdat.rp_renderer == 'Deferred':
 | |
|         # Copy deferred shader so that it can include compiled.inc
 | |
|         line_deferred_src = os.path.join(sdk_path, 'leenkx', 'Shaders', 'debug_draw', 'line_deferred.frag.glsl')
 | |
|         line_deferred_dst = os.path.join(shaders_path, 'line_deferred.frag.glsl')
 | |
|         shutil.copyfile(line_deferred_src, line_deferred_dst)
 | |
| 
 | |
|     for ref in assets.shader_passes:
 | |
|         for s in assets.shader_passes_assets[ref]:
 | |
|             assets.add_shader(shaders_path + '/' + s + '.glsl')
 | |
|     for file in assets.shaders_external:
 | |
|         name = file.split('/')[-1].split('\\')[-1]
 | |
|         target = build_dir + '/compiled/Shaders/' + name
 | |
|         if not os.path.exists(target):
 | |
|             shutil.copy(file, target)
 | |
|     state.last_world_defs = wrd.world_defs
 | |
| 
 | |
|     # Reset path
 | |
|     os.chdir(fp)
 | |
| 
 | |
|     # Copy std shaders
 | |
|     if not os.path.isdir(build_dir + '/compiled/Shaders/std'):
 | |
|         shutil.copytree(raw_shaders_path + 'std', build_dir + '/compiled/Shaders/std')
 | |
| 
 | |
|     # Write config.lnx
 | |
|     resx, resy = lnx.utils.get_render_resolution(lnx.utils.get_active_scene())
 | |
|     if wrd.lnx_write_config:
 | |
|         write_data.write_config(resx, resy)
 | |
| 
 | |
|     # Change project version (Build, Publish)
 | |
|     if (not state.is_play) and (wrd.lnx_project_version_autoinc):
 | |
|         wrd.lnx_project_version = lnx.utils.change_version_project(wrd.lnx_project_version)
 | |
| 
 | |
|     # Write khafile.js
 | |
|     write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, export_network, state.is_publish, LeenkxExporter.import_traits)
 | |
| 
 | |
|     # Write Main.hx - depends on write_khafilejs for writing number of assets
 | |
|     scene_name = lnx.utils.get_project_scene_name()
 | |
|     write_data.write_mainhx(scene_name, resx, resy, state.is_play, state.is_publish)
 | |
|     if scene_name != state.last_scene or resx != state.last_resx or resy != state.last_resy:
 | |
|         wrd.lnx_recompile = True
 | |
|         state.last_resx = resx
 | |
|         state.last_resy = resy
 | |
|         state.last_scene = scene_name
 | |
| 
 | |
| def compile(assets_only=False):
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
|     fp = lnx.utils.get_fp()
 | |
|     os.chdir(fp)
 | |
| 
 | |
|     node_path = lnx.utils.get_node_path()
 | |
|     khamake_path = lnx.utils.get_khamake_path()
 | |
|     cmd = [node_path, khamake_path]
 | |
| 
 | |
|     # Custom exporter
 | |
|     if state.target == "custom":
 | |
|         if len(wrd.lnx_exporterlist) > 0:
 | |
|             item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | |
|             if item.lnx_project_target == 'custom' and item.lnx_project_khamake != '':
 | |
|                 for s in item.lnx_project_khamake.split(' '):
 | |
|                     cmd.append(s)
 | |
|         state.proc_build = run_proc(cmd, build_done)
 | |
|     else:
 | |
|         target_name = state.target
 | |
|         kha_target_name = lnx.utils.get_kha_target(target_name)
 | |
|         if kha_target_name != '':
 | |
|             cmd.append(kha_target_name)
 | |
|         ffmpeg_path = lnx.utils.get_ffmpeg_path()
 | |
|         if ffmpeg_path not in (None, ''):
 | |
|             cmd.append('--ffmpeg')
 | |
|             cmd.append(ffmpeg_path) # '"' + ffmpeg_path + '"'
 | |
| 
 | |
|         state.export_gapi = lnx.utils.get_gapi()
 | |
|         cmd.append('-g')
 | |
|         cmd.append(state.export_gapi)
 | |
|         # Windows - Set Visual Studio Version
 | |
|         if state.target.startswith('windows'):
 | |
|             cmd.append('--visualstudio')
 | |
|             cmd.append(lnx.utils_vs.version_to_khamake_id[wrd.lnx_project_win_list_vs])
 | |
| 
 | |
|         if lnx.utils.get_legacy_shaders() or 'ios' in state.target:
 | |
|             if 'html5' in state.target or 'ios' in state.target:
 | |
|                 pass
 | |
|             else:
 | |
|                 cmd.append('--shaderversion')
 | |
|                 cmd.append('110')
 | |
|         elif 'android' in state.target or 'html5' in state.target:
 | |
|             cmd.append('--shaderversion')
 | |
|             cmd.append('300')
 | |
|         else:
 | |
|             cmd.append('--shaderversion')
 | |
|             cmd.append('330')
 | |
| 
 | |
|         if '_VR' in wrd.world_defs:
 | |
|             cmd.append('--vr')
 | |
|             cmd.append('webvr')
 | |
| 
 | |
|         if lnx.utils.get_pref_or_default('khamake_debug', False):
 | |
|             cmd.append('--debug')
 | |
| 
 | |
|         if lnx.utils.get_rp().rp_renderer == 'Raytracer':
 | |
|             cmd.append('--raytrace')
 | |
|             cmd.append('dxr')
 | |
|             dxc_path = fp + '/HlslShaders/dxc.exe'
 | |
|             subprocess.Popen([dxc_path, '-Zpr', '-Fo', fp + '/Bundled/raytrace.cso', '-T', 'lib_6_3', fp + '/HlslShaders/raytrace.hlsl']).wait()
 | |
| 
 | |
|         if lnx.utils.get_khamake_threads() != 1:
 | |
|             cmd.append('--parallelAssetConversion')
 | |
|             cmd.append(str(lnx.utils.get_khamake_threads()))
 | |
| 
 | |
|         compilation_server = False
 | |
| 
 | |
|         cmd.append('--to')
 | |
|         if (kha_target_name == 'krom' and not state.is_publish) or (kha_target_name == 'html5' and not state.is_publish):
 | |
|             cmd.append(lnx.utils.build_dir() + '/debug')
 | |
|             # Start compilation server
 | |
|             if kha_target_name == 'krom' and lnx.utils.get_compilation_server() and not assets_only and wrd.lnx_cache_build:
 | |
|                 compilation_server = True
 | |
|                 lnx.lib.server.run_haxe(lnx.utils.get_haxe_path())
 | |
|         else:
 | |
|             cmd.append(lnx.utils.build_dir())
 | |
| 
 | |
|         if not wrd.lnx_verbose_output:
 | |
|             cmd.append("--quiet")
 | |
| 
 | |
|         #Project needs to be compiled at least once
 | |
|         #before compilation server can work
 | |
|         if not os.path.exists(lnx.utils.build_dir() + '/debug/krom/krom.js') and not state.is_publish:
 | |
|             state.proc_build = run_proc(cmd, build_done)
 | |
|         else:
 | |
|             if assets_only or compilation_server:
 | |
|                 cmd.append('--nohaxe')
 | |
|                 cmd.append('--noproject')
 | |
|             if len(wrd.lnx_exporterlist) > 0:
 | |
|                 item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | |
|                 if item.lnx_project_khamake != "":
 | |
|                     for s in item.lnx_project_khamake.split(" "):
 | |
|                         cmd.append(s)
 | |
|             state.proc_build = run_proc(cmd, assets_done if compilation_server else build_done)
 | |
|             if bpy.app.background:
 | |
|                 if state.proc_build.returncode == 0:
 | |
|                     build_success()
 | |
|                 else:
 | |
|                     log.error('Build failed')
 | |
| 
 | |
| def build(target, is_play=False, is_publish=False, is_export=False):
 | |
|     global profile_time
 | |
|     profile_time = time.time()
 | |
| 
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
| 
 | |
|     if is_play and wrd.lnx_runtime == 'Hashlink':
 | |
|         current_os = lnx.utils.get_os()
 | |
|         if current_os == 'win':
 | |
|             target = 'windows-hl'
 | |
|         elif current_os == 'linux':
 | |
|             target = 'linux-hl'
 | |
|         elif current_os == 'macos':
 | |
|             target = 'macos-hl'
 | |
|         else:
 | |
|             log.error(f"Unsupported OS '{current_os}' for Hashlink runtime.")
 | |
| 
 | |
|     state.target = target
 | |
|     state.is_play = is_play
 | |
|     state.is_publish = is_publish
 | |
|     state.is_export = is_export
 | |
| 
 | |
|     # Save blend
 | |
|     if lnx.utils.get_save_on_build():
 | |
|         bpy.ops.wm.save_mainfile()
 | |
| 
 | |
|     log.clear(clear_warnings=True, clear_errors=True)
 | |
| 
 | |
|     # Set camera in active scene
 | |
|     active_scene = lnx.utils.get_active_scene()
 | |
|     if active_scene.camera == None:
 | |
|         for o in active_scene.objects:
 | |
|             if o.type == 'CAMERA':
 | |
|                 active_scene.camera = o
 | |
|                 break
 | |
| 
 | |
|     # Get paths
 | |
|     sdk_path = lnx.utils.get_sdk_path()
 | |
|     raw_shaders_path = sdk_path + '/leenkx/Shaders/'
 | |
| 
 | |
|     # Set dir
 | |
|     fp = lnx.utils.get_fp()
 | |
|     os.chdir(fp)
 | |
| 
 | |
|     # Create directories
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
|     sources_path = 'Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
 | |
|     if not os.path.exists(sources_path):
 | |
|         os.makedirs(sources_path)
 | |
| 
 | |
|     # Save external scripts edited inside Blender
 | |
|     write_texts = False
 | |
|     for text in bpy.data.texts:
 | |
|         if text.filepath != '' and text.is_dirty:
 | |
|             write_texts = True
 | |
|             break
 | |
|     if write_texts:
 | |
|         area = bpy.context.area
 | |
|         if area is not None:
 | |
|             old_type = area.type
 | |
|             area.type = 'TEXT_EDITOR'
 | |
|             for text in bpy.data.texts:
 | |
|                 if text.filepath != '' and text.is_dirty and os.path.isfile(text.filepath):
 | |
|                     area.spaces[0].text = text
 | |
|                     bpy.ops.text.save()
 | |
|             area.type = old_type
 | |
| 
 | |
|     # Save internal Haxe scripts
 | |
|     for text in bpy.data.texts:
 | |
|         if text.filepath == '' and text.name[-3:] == '.hx':
 | |
|             with open('Sources/' + lnx.utils.safestr(wrd.lnx_project_package) + '/' + text.name, 'w', encoding='utf-8') as f:
 | |
|                 f.write(text.as_string())
 | |
| 
 | |
|     # Export data
 | |
|     export_data(fp, sdk_path)
 | |
| 
 | |
|     if state.target == 'html5':
 | |
|         w, h = lnx.utils.get_render_resolution(lnx.utils.get_active_scene())
 | |
|         write_data.write_indexhtml(w, h, is_publish)
 | |
|         # Bundle files from include dir
 | |
|         if os.path.isdir('include'):
 | |
|             dest  = '/html5/' if is_publish else '/debug/html5/'
 | |
|             for fn in glob.iglob(os.path.join('include', '**'), recursive=False):
 | |
|                 shutil.copy(fn, lnx.utils.build_dir() + dest + os.path.basename(fn))
 | |
| 
 | |
| def play_done():
 | |
|     """Called if the player was stopped/terminated."""
 | |
|     if state.proc_play is not None:
 | |
|         if state.proc_play.returncode != 0:
 | |
|             log.warn(f'Player exited code {state.proc_play.returncode}')
 | |
|     state.proc_play = None
 | |
|     state.redraw_ui = True
 | |
|     log.clear()
 | |
|     live_patch.stop()
 | |
| 
 | |
| def assets_done():
 | |
|     if state.proc_build == None:
 | |
|         return
 | |
|     result = state.proc_build.poll()
 | |
|     if result == 0:
 | |
|         # Connect to the compilation server
 | |
|         os.chdir(lnx.utils.build_dir() + '/debug/')
 | |
|         cmd = [lnx.utils.get_haxe_path(), '--connect', '6000', 'project-krom.hxml']
 | |
|         state.proc_build = run_proc(cmd, compilation_server_done)
 | |
|     else:
 | |
|         state.proc_build = None
 | |
|         state.redraw_ui = True
 | |
|         log.error('Build failed, check console')
 | |
| 
 | |
| def compilation_server_done():
 | |
|     if state.proc_build == None:
 | |
|         return
 | |
|     result = state.proc_build.poll()
 | |
|     if result == 0:
 | |
|         if os.path.exists('krom/krom.js.temp'):
 | |
|             os.chmod('krom/krom.js', stat.S_IWRITE)
 | |
|             os.remove('krom/krom.js')
 | |
|             os.rename('krom/krom.js.temp', 'krom/krom.js')
 | |
|         build_done()
 | |
|     else:
 | |
|         state.proc_build = None
 | |
|         state.redraw_ui = True
 | |
|         log.error('Build failed, check console')
 | |
| 
 | |
| def build_done():
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
|     log.info('Finished in {:0.3f}s'.format(time.time() - profile_time))
 | |
|     if log.num_warnings > 0:
 | |
|         log.print_warn(f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation')
 | |
|     if state.proc_build is None:
 | |
|         return
 | |
|     result = state.proc_build.poll()
 | |
|     state.proc_build = None
 | |
|     state.redraw_ui = True
 | |
|     if result == 0:
 | |
|         bpy.data.worlds['Lnx'].lnx_recompile = False
 | |
|         build_success()
 | |
|     else:
 | |
|         log.error('Build failed, check console')
 | |
| 
 | |
| 
 | |
| def runtime_to_target():
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
|     if wrd.lnx_runtime == 'Krom':
 | |
|         return 'krom'
 | |
|     return 'html5'
 | |
| 
 | |
| def get_khajs_path(target):
 | |
|     if target == 'krom':
 | |
|         return lnx.utils.build_dir() + '/debug/krom/krom.js'
 | |
|     return lnx.utils.build_dir() + '/debug/html5/kha.js'
 | |
| 
 | |
| def play():
 | |
|     global scripts_mtime
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
| 
 | |
|     build(target=runtime_to_target(), is_play=True)
 | |
| 
 | |
|     khajs_path = get_khajs_path(state.target)
 | |
|     if not wrd.lnx_cache_build or \
 | |
|        not os.path.isfile(khajs_path) or \
 | |
|        assets.khafile_defs_last != assets.khafile_defs or \
 | |
|        state.last_target != state.target:
 | |
|         wrd.lnx_recompile = True
 | |
| 
 | |
|     state.last_target = state.target
 | |
| 
 | |
|     # Trait sources modified
 | |
|     state.mod_scripts = []
 | |
|     script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
 | |
|     if os.path.isdir(script_path):
 | |
|         new_mtime = scripts_mtime
 | |
|         for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
 | |
|             mtime = os.path.getmtime(fn)
 | |
|             if scripts_mtime < mtime:
 | |
|                 lnx.utils.fetch_script_props(fn) # Trait props
 | |
|                 fn = fn.split('Sources/')[1]
 | |
|                 fn = fn[:-3] #.hx
 | |
|                 fn = fn.replace('/', '.')
 | |
|                 state.mod_scripts.append(fn)
 | |
|                 wrd.lnx_recompile = True
 | |
|                 if new_mtime < mtime:
 | |
|                     new_mtime = mtime
 | |
|         scripts_mtime = new_mtime
 | |
|         if len(state.mod_scripts) > 0: # Trait props
 | |
|             lnx.utils.fetch_trait_props()
 | |
| 
 | |
|     compile(assets_only=(not wrd.lnx_recompile))
 | |
| 
 | |
| def build_success():
 | |
|     log.clear()
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
| 
 | |
|     if state.is_play:
 | |
|         cmd = []
 | |
|         width, height = lnx.utils.get_render_resolution(lnx.utils.get_active_scene())
 | |
|         if wrd.lnx_runtime == 'Browser':
 | |
|             os.chdir(lnx.utils.get_fp())
 | |
|             prefs = lnx.utils.get_lnx_preferences()
 | |
|             host = 'localhost'
 | |
|             t = threading.Thread(name='localserver',
 | |
|                 target=lnx.lib.server.run_tcp,
 | |
|                 args=(prefs.html5_server_port,
 | |
|                 prefs.html5_server_log),
 | |
|                 daemon=True)
 | |
|             t.start()
 | |
|             build_dir = lnx.utils.build_dir()
 | |
|             path = '{}/debug/html5/'.format(build_dir)
 | |
|             url = 'http://{}:{}/{}'.format(host, prefs.html5_server_port, path)
 | |
|             browser = webbrowser.get()
 | |
|             browsername = None
 | |
|             if hasattr(browser, "name"):
 | |
|                 browsername = getattr(browser,'name')
 | |
|             elif hasattr(browser,"_name"):
 | |
|                 browsername = getattr(browser,'_name')
 | |
|             envvar = 'LEENKX_PLAY_HTML5'
 | |
|             if envvar in os.environ:
 | |
|                 envcmd = os.environ[envvar]
 | |
|                 if len(envcmd) == 0:
 | |
|                     log.warn(f"Your {envvar} environment variable is set to an empty string")
 | |
|                 else:
 | |
|                     tplstr = Template(envcmd).safe_substitute({
 | |
|                         'host': host,
 | |
|                         'port': prefs.html5_server_port,
 | |
|                         'width': width,
 | |
|                         'height': height,
 | |
|                         'url': url,
 | |
|                         'path': path,
 | |
|                         'dir': build_dir,
 | |
|                         'browser': browsername
 | |
|                     })
 | |
|                     cmd = re.split(' +', tplstr)
 | |
|             if len(cmd) == 0:
 | |
|                 if browsername in (None, '', 'default'):
 | |
|                     webbrowser.open(url)
 | |
|                     return
 | |
|                 cmd = [browsername, url]
 | |
|         elif wrd.lnx_runtime == 'Krom':
 | |
|             if wrd.lnx_live_patch:
 | |
|                 live_patch.start()
 | |
|                 open(lnx.utils.get_fp_build() + '/debug/krom/krom.patch', 'w', encoding='utf-8').close()
 | |
|             krom_location, krom_path = lnx.utils.krom_paths()
 | |
|             path = lnx.utils.get_fp_build() + '/debug/krom'
 | |
|             path_resources = path + '-resources'
 | |
|             pid = os.getpid()
 | |
|             os.chdir(krom_location)
 | |
|             envvar = 'LEENKX_PLAY_KROM'
 | |
|             if envvar in os.environ:
 | |
|                 envcmd = os.environ[envvar]
 | |
|                 if len(envcmd) == 0:
 | |
|                     log.warn(f"Your {envvar} environment variable is set to an empty string")
 | |
|                 else:
 | |
|                     tplstr = Template(envcmd).safe_substitute({
 | |
|                         'pid': pid,
 | |
|                         'audio': wrd.lnx_audio != 'Disabled',
 | |
|                         'location': krom_location,
 | |
|                         'krom_path': krom_path,
 | |
|                         'path': path,
 | |
|                         'resources': path_resources,
 | |
|                         'width': width,
 | |
|                         'height': height
 | |
|                     })
 | |
|                     cmd = re.split(' +', tplstr)
 | |
|             if len(cmd) == 0:
 | |
|                 cmd = [krom_path, path, path_resources]
 | |
|                 if lnx.utils.get_os() == 'win':
 | |
|                     cmd.append('--consolepid')
 | |
|                     cmd.append(str(pid))
 | |
|                 if wrd.lnx_audio == 'Disabled':
 | |
|                     cmd.append('--nosound')
 | |
|                     
 | |
|         elif state.target.startswith(('windows-hl', 'linux-hl', 'macos-hl')):
 | |
|             log.info(f"Runtime Hashlink/C target: {state.target}")
 | |
| 
 | |
|             hl_build_dir, _, _ = lnx.utils.hashlink_paths(state.target)
 | |
| 
 | |
|             if not hl_build_dir:
 | |
|                  log.error(f"Could not find build directory for target {state.target}. Playback aborted.")
 | |
|                  return 
 | |
| 
 | |
|             if state.target == 'windows-hl':
 | |
|                 vs_version_major = wrd.lnx_project_win_list_vs
 | |
|                 build_mode = wrd.lnx_project_win_build_mode # Debug or Release
 | |
|                 build_arch = wrd.lnx_project_win_build_arch # x64 or x86 (maps to Win32 for MSBuild)
 | |
|                 platform = 'x64' if build_arch == 'x64' else 'Win32' # MSBuild uses Win32 for x86
 | |
| 
 | |
|                 installation = lnx.utils_vs.get_installed_version(vs_version_major, re_fetch=True)
 | |
|                 if installation is None:
 | |
|                     vs_info = lnx.utils_vs.get_supported_version(vs_version_major)
 | |
|                     log.error(f'Visual Studio {vs_info["name"]} not found. Cannot compile {state.target}.')
 | |
|                     return
 | |
|                 msbuild_path = os.path.join(installation['path'], 'MSBuild', 'Current', 'Bin', 'MSBuild.exe')
 | |
|                 if not os.path.isfile(msbuild_path):
 | |
|                     msbuild_path = os.path.join(installation['path'], 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') # VS 2017 fallback
 | |
|                     if not os.path.isfile(msbuild_path):
 | |
|                          log.error(f'MSBuild.exe not found for {installation["name"]}. Cannot compile {state.target}.')
 | |
|                          return
 | |
| 
 | |
|                 proj_name = lnx.utils.blend_name()
 | |
|                 vcxproj_path = os.path.join(hl_build_dir, proj_name + '.vcxproj')
 | |
|                 if not os.path.isfile(vcxproj_path):
 | |
|                     found_vcxproj = None
 | |
|                     for file in os.listdir(hl_build_dir):
 | |
|                         if file.endswith(".vcxproj"):
 | |
|                             found_vcxproj = os.path.join(hl_build_dir, file)
 | |
|                             log.warn(f"Could not find '{proj_name}.vcxproj', using found '{file}' instead.")
 | |
|                             break
 | |
|                     if not found_vcxproj:
 | |
|                         log.error(f'.vcxproj file not found in {hl_build_dir}. Cannot compile.')
 | |
|                         return
 | |
|                     vcxproj_path = found_vcxproj
 | |
|                     proj_name = os.path.splitext(os.path.basename(vcxproj_path))[0] 
 | |
| 
 | |
|                 msbuild_cmd = [
 | |
|                     msbuild_path,
 | |
|                     vcxproj_path,
 | |
|                     f'/p:Configuration={build_mode}',
 | |
|                     f'/p:Platform={platform}',
 | |
|                     '/m' 
 | |
|                 ]
 | |
| 
 | |
|                 log.info(f"Compiling {state.target} project with MSBuild...")
 | |
|                 log.info(f"Command: {' '.join(msbuild_cmd)}")
 | |
|                 compile_success = False
 | |
|                 try:
 | |
|                     compile_result = subprocess.run(msbuild_cmd, cwd=hl_build_dir, check=False, capture_output=True, text=True)
 | |
|                     if compile_result.returncode == 0:
 | |
|                         log.info(f"MSBuild compilation successful.")
 | |
|                         compile_success = True
 | |
|                     else:
 | |
|                         log.error(f"MSBuild compilation failed (Exit Code: {compile_result.returncode}).")
 | |
|                         log.error(f"MSBuild Output:\n{compile_result.stdout}")
 | |
|                         log.error(f"MSBuild Errors:\n{compile_result.stderr}")
 | |
|                 except Exception as e:
 | |
|                     log.error(f"Error running MSBuild: {e}")
 | |
|                     traceback.print_exc()
 | |
| 
 | |
|                 if not compile_success:
 | |
|                     return
 | |
| 
 | |
|                 exe_path = os.path.join(hl_build_dir, platform, build_mode, proj_name + '.exe')
 | |
|                 if not os.path.isfile(exe_path):
 | |
|                     exe_path = os.path.join(hl_build_dir, build_mode, proj_name + '.exe')
 | |
|                     if not os.path.isfile(exe_path):
 | |
|                         log.error(f'Compiled executable not found at expected location: {exe_path} (or variants). Cannot run.')
 | |
|                         return
 | |
| 
 | |
|                 log.info(f"Found compiled executable: {exe_path}")
 | |
|                 
 | |
|                 dest_exe_name = proj_name + '.exe'
 | |
|                 base_build_dir = lnx.utils.get_fp_build()
 | |
|                 dest_dir = os.path.join(base_build_dir, state.target)
 | |
|                 dest_path = os.path.join(dest_dir, dest_exe_name)
 | |
|                 try:
 | |
|                     shutil.move(exe_path, dest_path)
 | |
|                     cmd = [dest_path]
 | |
|                 except Exception as e:
 | |
|                     cmd = [exe_path]
 | |
|                 os.chdir(dest_dir)
 | |
| 
 | |
|             elif state.target in ('linux-hl', 'macos-hl'):
 | |
|                 wrd = bpy.data.worlds['Lnx']
 | |
|                 paths = lnx.utils.hashlink_paths(state.target)
 | |
|                 hl_build_dir = paths[0]
 | |
|                 # TO DO switch from default Release
 | |
|                 build_mode = 'Release'
 | |
|                 proj_name = lnx.utils.blend_name()
 | |
|                 exe_path = str(hl_build_dir + "/" + build_mode) 
 | |
|                 if not exe_path:
 | |
|                     log.error(f"Build finished, but could not find the executable for {state.target}.")
 | |
|                     return
 | |
|                 makefile_path = os.path.join(exe_path, 'makefile')
 | |
|                 if not os.path.isfile(makefile_path):
 | |
|                     log.error(f"Makefile not found at '{makefile_path}'. Cannot compile C code.")
 | |
|                     return
 | |
| 
 | |
|                 make_cmd = ['make']
 | |
| 
 | |
|                 log.info(f"Compiling C code using 'make' in directory '{exe_path}'...")
 | |
|                 log.info(f"Make command: {' '.join(make_cmd)}")
 | |
|                 try:
 | |
|                     result = subprocess.run(make_cmd, cwd=exe_path, check=True, capture_output=True, text=True, encoding='utf-8')
 | |
|                     log.info("'make' compilation successful.")
 | |
|                 except subprocess.CalledProcessError as e:
 | |
|                     log.error(f"'make' compilation failed with return code {e.returncode}.")
 | |
|                     log.error(f"Make Error Output:\n{e.stderr}")
 | |
|                     return 
 | |
|                 except FileNotFoundError:
 | |
|                     log.error("'make' command not found. Ensure 'make' is installed and in your system's PATH.")
 | |
|                     return
 | |
|                 except Exception as e:
 | |
|                     log.error(f"An unexpected error occurred running make: {e}")
 | |
|                     return
 | |
| 
 | |
|                 log.info(f"Found compiled executable: {exe_path}")
 | |
| 
 | |
|                 dest_exe_name = lnx.utils.safesrc(wrd.lnx_project_name + '-' + wrd.lnx_project_version)
 | |
|                 base_build_dir = lnx.utils.get_fp_build()
 | |
|                 dest_dir = os.path.join(base_build_dir, state.target) 
 | |
|                 og_path = os.path.join(exe_path, dest_exe_name)
 | |
|                 dest_path = os.path.join(dest_dir, dest_exe_name)
 | |
|                 os.makedirs(dest_dir, exist_ok=True)
 | |
| 
 | |
|                 try:
 | |
|                     log.info(f"Moving '{og_path}' to '{dest_dir}'...")
 | |
|                     shutil.move(og_path, dest_dir) 
 | |
|                     cmd = [dest_path] 
 | |
|                 except Exception as e:
 | |
|                     log.error(f"Failed to move executable: {e}. Attempting to run from original location.")
 | |
|                     cmd = [exe_path]
 | |
|                 os.chdir(dest_dir)
 | |
|                 log.info(f"Hashlink final command: {' '.join(cmd)}")
 | |
|         try:
 | |
|             state.proc_play = run_proc(cmd, play_done)
 | |
|         except Exception:
 | |
|             traceback.print_exc()
 | |
|             log.warn('Failed to start player, command and exception have been printed to console above')
 | |
|             if wrd.lnx_runtime == 'Browser':
 | |
|                 webbrowser.open(url)
 | |
| 
 | |
|     elif state.is_publish:
 | |
|         sdk_path = lnx.utils.get_sdk_path()
 | |
|         target_name = lnx.utils.get_kha_target(state.target)
 | |
|         files_path = os.path.join(lnx.utils.get_fp_build(), target_name)
 | |
| 
 | |
|         if target_name in ('html5', 'krom') and wrd.lnx_minify_js:
 | |
|             # Minify JS
 | |
|             minifier_path = sdk_path + '/lib/leenkx_tools/uglifyjs/bin/uglifyjs'
 | |
|             if target_name == 'html5':
 | |
|                 jsfile = files_path + '/kha.js'
 | |
|             else:
 | |
|                 jsfile = files_path + '/krom.js'
 | |
|             args = [lnx.utils.get_node_path(), minifier_path, jsfile, '-o', jsfile]
 | |
|             proc = subprocess.Popen(args)
 | |
|             proc.wait()
 | |
| 
 | |
|         if target_name == 'krom':
 | |
|             # Copy Krom binaries
 | |
|             if state.target == 'krom-windows':
 | |
|                 gapi = state.export_gapi
 | |
|                 ext = '' if gapi == 'direct3d11' else '_' + gapi
 | |
|                 krom_location = sdk_path + '/Krom/Krom' + ext + '.exe'
 | |
|                 shutil.copy(krom_location, files_path + '/Krom.exe')
 | |
|                 krom_exe = lnx.utils.safestr(wrd.lnx_project_name) + '.exe'
 | |
|                 os.rename(files_path + '/Krom.exe', files_path + '/' + krom_exe)
 | |
|             elif state.target == 'krom-linux':
 | |
|                 krom_location = sdk_path + '/Krom/Krom'
 | |
|                 shutil.copy(krom_location, files_path)
 | |
|                 krom_exe = lnx.utils.safestr(wrd.lnx_project_name)
 | |
|                 os.rename(files_path + '/Krom', files_path + '/' + krom_exe)
 | |
|                 krom_exe = './' + krom_exe
 | |
|             else:
 | |
|                 krom_location = sdk_path + '/Krom/Krom.app'
 | |
|                 shutil.copytree(krom_location, files_path + '/Krom.app')
 | |
|                 game_files = os.listdir(files_path)
 | |
|                 for f in game_files:
 | |
|                     f = files_path + '/' + f
 | |
|                     if os.path.isfile(f):
 | |
|                         shutil.move(f, files_path + '/Krom.app/Contents/MacOS')
 | |
|                 krom_exe = lnx.utils.safestr(wrd.lnx_project_name) + '.app'
 | |
|                 os.rename(files_path + '/Krom.app', files_path + '/' + krom_exe)
 | |
| 
 | |
|             # Rename
 | |
|             ext = state.target.split('-')[-1] # krom-windows
 | |
|             new_files_path = files_path + '-' + ext
 | |
|             os.rename(files_path, new_files_path)
 | |
|             files_path = new_files_path
 | |
| 
 | |
|         if target_name == 'html5':
 | |
|             project_path = files_path
 | |
|             print('Exported HTML5 package to ' + project_path)
 | |
|         elif target_name.startswith('ios') or target_name.startswith('osx'): # TODO: to macos
 | |
|             project_path = files_path + '-build'
 | |
|             print('Exported XCode project to ' + project_path)
 | |
|         elif target_name.startswith('windows'):
 | |
|             project_path = files_path + '-build'
 | |
|             vs_info = lnx.utils_vs.get_supported_version(wrd.lnx_project_win_list_vs)
 | |
|             print(f'Exported {vs_info["name"]} project to {project_path}')
 | |
|         elif target_name.startswith('android'):
 | |
|             project_name = lnx.utils.safesrc(wrd.lnx_project_name + '-' + wrd.lnx_project_version)
 | |
|             project_path = os.path.join(files_path + '-build', project_name)
 | |
|             print('Exported Android Studio project to ' + project_path)
 | |
|         elif target_name.startswith('krom'):
 | |
|             project_path = files_path
 | |
|             print('Exported Krom package to ' + project_path)
 | |
|         else:
 | |
|             project_path = files_path + '-build'
 | |
|             print('Exported makefiles to ' + project_path)
 | |
| 
 | |
|         if not bpy.app.background and lnx.utils.get_lnx_preferences().open_build_directory:
 | |
|             lnx.utils.open_folder(project_path)
 | |
| 
 | |
|         # Android build APK
 | |
|         if target_name.startswith('android'):
 | |
|             if (lnx.utils.get_project_android_build_apk()) and (len(lnx.utils.get_android_sdk_root_path()) > 0):
 | |
|                 print("\nBuilding APK")
 | |
|                 # Check settings
 | |
|                 path_sdk = lnx.utils.get_android_sdk_root_path()
 | |
|                 if len(path_sdk) > 0:
 | |
|                     # Check Environment Variables - ANDROID_SDK_ROOT
 | |
|                     if os.getenv('ANDROID_SDK_ROOT') is None:
 | |
|                         # Set value from settings
 | |
|                         os.environ['ANDROID_SDK_ROOT'] = path_sdk
 | |
|                 else:
 | |
|                     project_path = ''
 | |
| 
 | |
|                 # Build start
 | |
|                 if len(project_path) > 0:
 | |
|                     os.chdir(project_path) # set work folder
 | |
|                     if lnx.utils.get_os_is_windows():
 | |
|                         state.proc_publish_build = run_proc(os.path.join(project_path, "gradlew.bat assembleDebug"), done_gradlew_build)
 | |
|                     else:
 | |
|                         cmd = shlex.split(os.path.join(project_path, "gradlew assembleDebug"))
 | |
|                         state.proc_publish_build = run_proc(cmd, done_gradlew_build)
 | |
|                 else:
 | |
|                     print('\nBuilding APK Warning: ANDROID_SDK_ROOT is not specified in environment variables and "Android SDK Path" setting is not specified in preferences: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path" in the preferences, then repeat operation "Publish"')
 | |
| 
 | |
|         # HTML5 After Publish
 | |
|         if target_name.startswith('html5'):
 | |
|             if len(lnx.utils.get_html5_copy_path()) > 0 and (wrd.lnx_project_html5_copy):
 | |
|                 project_name = lnx.utils.safesrc(wrd.lnx_project_name +'-'+ wrd.lnx_project_version)
 | |
|                 dst = os.path.join(lnx.utils.get_html5_copy_path(), project_name)
 | |
|                 if os.path.exists(dst):
 | |
|                     shutil.rmtree(dst)
 | |
|                 try:
 | |
|                     shutil.copytree(project_path, dst)
 | |
|                     print("Copied files to " + dst)
 | |
|                 except OSError as exc:
 | |
|                     if exc.errno == errno.ENOTDIR:
 | |
|                         shutil.copy(project_path, dst)
 | |
|                     else: raise
 | |
|                 if len(lnx.utils.get_link_web_server()) and (wrd.lnx_project_html5_start_browser):
 | |
|                     link_html5_app = lnx.utils.get_link_web_server() +'/'+ project_name
 | |
|                     print("Running a browser with a link " + link_html5_app)
 | |
|                     webbrowser.open(link_html5_app)
 | |
| 
 | |
|         # Windows After Publish
 | |
|         if target_name.startswith('windows') and wrd.lnx_project_win_build != 'nothing' and lnx.utils.get_os_is_windows():
 | |
|             project_name = lnx.utils.safesrc(wrd.lnx_project_name + '-' + wrd.lnx_project_version)
 | |
| 
 | |
|             # Open in Visual Studio
 | |
|             if wrd.lnx_project_win_build == 'open':
 | |
|                 print('\nOpening in Visual Studio: ' + lnx.utils_vs.get_sln_path())
 | |
|                 _ = lnx.utils_vs.open_project_in_vs(wrd.lnx_project_win_list_vs)
 | |
| 
 | |
|             # Compile
 | |
|             elif wrd.lnx_project_win_build.startswith('compile'):
 | |
|                 if wrd.lnx_project_win_build == 'compile':
 | |
|                     print('\nCompiling project ' + lnx.utils_vs.get_vcxproj_path())
 | |
|                 elif wrd.lnx_project_win_build == 'compile_and_run':
 | |
|                     print('\nCompiling and running project ' + lnx.utils_vs.get_vcxproj_path())
 | |
| 
 | |
|                 success = lnx.utils_vs.enable_vsvars_env(wrd.lnx_project_win_list_vs, done_vs_vars)
 | |
|                 if not success:
 | |
|                     state.redraw_ui = True
 | |
|                     log.error('Compile failed, check console')
 | |
| 
 | |
| 
 | |
| def done_gradlew_build():
 | |
|     if state.proc_publish_build is None:
 | |
|         return
 | |
|     result = state.proc_publish_build.poll()
 | |
|     if result == 0:
 | |
|         state.proc_publish_build = None
 | |
| 
 | |
|         wrd = bpy.data.worlds['Lnx']
 | |
|         path_apk = os.path.join(lnx.utils.get_fp_build(), lnx.utils.get_kha_target(state.target))
 | |
|         project_name = lnx.utils.safesrc(wrd.lnx_project_name +'-'+ wrd.lnx_project_version)
 | |
|         path_apk = os.path.join(path_apk + '-build', project_name, 'app', 'build', 'outputs', 'apk', 'debug')
 | |
| 
 | |
|         print("\nBuild APK to " + path_apk)
 | |
|         # Rename APK
 | |
|         apk_name = 'app-debug.apk'
 | |
|         file_name = os.path.join(path_apk, apk_name)
 | |
|         if wrd.lnx_project_android_rename_apk:
 | |
|             apk_name = project_name + '.apk'
 | |
|             os.rename(file_name, os.path.join(path_apk, apk_name))
 | |
|             file_name = os.path.join(path_apk, apk_name)
 | |
|             print("\nRename APK to " + apk_name)
 | |
|         # Copy APK
 | |
|         if wrd.lnx_project_android_copy_apk:
 | |
|             shutil.copyfile(file_name, os.path.join(lnx.utils.get_android_apk_copy_path(), apk_name))
 | |
|             print("Copy APK to " + lnx.utils.get_android_apk_copy_path())
 | |
|         # Open directory with APK
 | |
|         if lnx.utils.get_android_open_build_apk_directory():
 | |
|             lnx.utils.open_folder(path_apk)
 | |
|         # Open directory after copy APK
 | |
|         if lnx.utils.get_android_apk_copy_open_directory():
 | |
|             lnx.utils.open_folder(lnx.utils.get_android_apk_copy_path())
 | |
|         # Running emulator
 | |
|         if wrd.lnx_project_android_run_avd:
 | |
|             run_android_emulators(lnx.utils.get_android_emulator_name())
 | |
|         state.redraw_ui = True
 | |
|     else:
 | |
|         state.proc_publish_build = None
 | |
|         state.redraw_ui = True
 | |
|         os.environ['ANDROID_SDK_ROOT'] = ''
 | |
|         log.error('Building the APK failed, check console')
 | |
| 
 | |
| def run_android_emulators(avd_name):
 | |
|     if len(avd_name.strip()) == 0:
 | |
|         return
 | |
|     print('\nRunning Emulator "'+ avd_name +'"')
 | |
|     path_file = lnx.utils.get_android_emulator_file()
 | |
|     if len(path_file) > 0:
 | |
|         if lnx.utils.get_os_is_windows():
 | |
|             run_proc(path_file + " -avd "+ avd_name, None)
 | |
|         else:
 | |
|             cmd = shlex.split(path_file + " -avd "+ avd_name)
 | |
|             run_proc(cmd, None)
 | |
|     else:
 | |
|         print('Update List Emulators Warning: File "'+ path_file +'" not found. Check that the variable ANDROID_SDK_ROOT is correct in environment variables or in "Android SDK Path" setting: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path", then repeat operation "Publish"')
 | |
| 
 | |
| 
 | |
| def done_vs_vars():
 | |
|     if state.proc_publish_build is None:
 | |
|         return
 | |
| 
 | |
|     result = state.proc_publish_build.poll()
 | |
|     if result == 0:
 | |
|         state.proc_publish_build = None
 | |
| 
 | |
|         wrd = bpy.data.worlds['Lnx']
 | |
|         success = lnx.utils_vs.compile_in_vs(wrd.lnx_project_win_list_vs, done_vs_build)
 | |
|         if not success:
 | |
|             state.proc_publish_build = None
 | |
|             state.redraw_ui = True
 | |
|             log.error('Compile failed, check console')
 | |
|     else:
 | |
|         state.proc_publish_build = None
 | |
|         state.redraw_ui = True
 | |
|         log.error('Compile failed, check console')
 | |
| 
 | |
| 
 | |
| def done_vs_build():
 | |
|     if state.proc_publish_build is None:
 | |
|         return
 | |
| 
 | |
|     result = state.proc_publish_build.poll()
 | |
|     if result == 0:
 | |
|         state.proc_publish_build = None
 | |
| 
 | |
|         wrd = bpy.data.worlds['Lnx']
 | |
|         project_path = os.path.join(lnx.utils.get_fp_build(), lnx.utils.get_kha_target(state.target)) + '-build'
 | |
|         if wrd.lnx_project_win_build_arch == 'x64':
 | |
|             path = os.path.join(project_path, 'x64', wrd.lnx_project_win_build_mode)
 | |
|         else:
 | |
|             path = os.path.join(project_path, wrd.lnx_project_win_build_mode)
 | |
|         print('\nCompilation completed in ' + path)
 | |
|         # Run
 | |
|         if wrd.lnx_project_win_build == 'compile_and_run':
 | |
|             # Copying the executable file
 | |
|             res_path = os.path.join(lnx.utils.get_fp_build(), lnx.utils.get_kha_target(state.target))
 | |
|             file_name = lnx.utils.safesrc(wrd.lnx_project_name +'-'+ wrd.lnx_project_version) + '.exe'
 | |
|             print('\nCopy the executable file from ' + path + ' to ' + res_path)
 | |
|             shutil.copyfile(os.path.join(path, file_name), os.path.join(res_path, file_name))
 | |
|             path = res_path
 | |
|             # Run project
 | |
|             cmd = os.path.join('"' + res_path, file_name + '"')
 | |
|             print('Run the executable file to ' + cmd)
 | |
|             os.chdir(res_path) # set work folder
 | |
|             subprocess.Popen(cmd, shell=True)
 | |
|         # Open Build Directory
 | |
|         if wrd.lnx_project_win_build_open:
 | |
|             lnx.utils.open_folder(path)
 | |
|         state.redraw_ui = True
 | |
|     else:
 | |
|         state.proc_publish_build = None
 | |
|         state.redraw_ui = True
 | |
|         log.error('Compile failed, check console')
 | |
| 
 | |
| def clean():
 | |
|     os.chdir(lnx.utils.get_fp())
 | |
|     wrd = bpy.data.worlds['Lnx']
 | |
| 
 | |
|     # Remove build and compiled data
 | |
|     try:
 | |
|         if os.path.isdir(lnx.utils.build_dir()):
 | |
|             shutil.rmtree(lnx.utils.build_dir(), onerror=remove_readonly)
 | |
|         if os.path.isdir(lnx.utils.get_fp() + '/build'): # Kode Studio build dir
 | |
|             shutil.rmtree(lnx.utils.get_fp() + '/build', onerror=remove_readonly)
 | |
|     except:
 | |
|         print('Leenkx Warning: Some files in the build folder are locked')
 | |
| 
 | |
|     # Remove compiled nodes
 | |
|     pkg_dir = lnx.utils.safestr(wrd.lnx_project_package).replace('.', '/')
 | |
|     nodes_path = 'Sources/' + pkg_dir + '/node/'
 | |
|     if os.path.isdir(nodes_path):
 | |
|         shutil.rmtree(nodes_path, onerror=remove_readonly)
 | |
| 
 | |
|     # Remove khafile/Main.hx
 | |
|     if os.path.isfile('khafile.js'):
 | |
|         os.remove('khafile.js')
 | |
|     if os.path.isfile('Sources/Main.hx'):
 | |
|         os.remove('Sources/Main.hx')
 | |
| 
 | |
|     # Remove Sources/ dir if empty
 | |
|     if os.path.exists('Sources/' + pkg_dir) and os.listdir('Sources/' + pkg_dir) == []:
 | |
|         shutil.rmtree('Sources/' + pkg_dir, onerror=remove_readonly)
 | |
|         if os.path.exists('Sources') and os.listdir('Sources') == []:
 | |
|             shutil.rmtree('Sources/', onerror=remove_readonly)
 | |
| 
 | |
|     # Remove Shape key Textures
 | |
|     if os.path.exists('MorphTargets/'):
 | |
|         shutil.rmtree('MorphTargets/', onerror=remove_readonly)
 | |
| 
 | |
|     # To recache signatures for batched materials
 | |
|     for mat in bpy.data.materials:
 | |
|         mat.signature = ''
 | |
|         mat.lnx_cached = False
 | |
| 
 | |
|     # Restart compilation server
 | |
|     if lnx.utils.get_compilation_server():
 | |
|         lnx.lib.server.kill_haxe()
 | |
| 
 | |
|     log.info('Project cleaned')
 |