Windows Settings: Visual Studio Version - select the studio version for which the project will be exported. Options: 2010, 2012, 2013, 2015, 2017, 2019. Default: 2019. Update - checks the installed versions of Visual Studio on the PC and adds (installed) to the version in the list if available (for information). Example: sample_vs_2 Action After Publishing - select an action after a successful publication. Options: Nothing - do nothing. Default value; Open In Visual Studio - open the project in the corresponding studio version; Compile - compilation of the project; Compile and Run - compile and run the project. Then the executable file will be copied to the windows-hl folder (where the resources are located). Mode - compilation mode. Options: Debug, Release. Default: Debug. Architecture - the architecture for which the application will be built. Options: x86, x64. Default: version of the user’s PC architecture. Compile Log Parameter - setting the output of messages during compilation: Summary - show the error and warning summary at the end. Default value; No Summary - don \ 't show the error and warning summary at the end; Warnings and Errors Only - show only warnings and errors; Warnings Only - show only warnings; Errors Only - show only errors. More details can be found here - MSBuild command-line reference (I took only part of the settings). Count CPU - specifies the maximum number of concurrent processes to use when building. More details can be found here - MSBuild command-line reference. The default is 1. Maximum value: the number of CPUs in the system (function multiprocessing.cpu_count()). Open Build Directory - open the folder with the application after a successful build. If the Compile and Run option is selected, then the executable file will be copied to the windows-hl folder (where the resources are located) and this folder will open. Otherwise, the folder where the given Visual Studio file is going will open. The user will also receive a message if the studio version selected for export and for opening in the studio or compilation is not on the PC. And a list of installed ones will be issued. Example: Visual Studio 2017 (version 15) not found. The following are installed on the PC: - Visual Studio Community 2019 (version 16.8.30711.63) To obtain information about the installed versions of Visual Studio, use the vswhere.exe utility (open source) included in Kha (located in the …\ArmorySDK\Kha\Kinc\Tools\kincmake\Data\windows).
903 lines
37 KiB
Python
Executable file
903 lines
37 KiB
Python
Executable file
import glob
|
|
import json
|
|
import os
|
|
import shutil
|
|
import time
|
|
import stat
|
|
import subprocess
|
|
import threading
|
|
import webbrowser
|
|
import shlex
|
|
import errno
|
|
|
|
import bpy
|
|
|
|
import arm.assets as assets
|
|
from arm.exporter import ArmoryExporter
|
|
import arm.lib.make_datas
|
|
import arm.lib.server
|
|
import arm.log as log
|
|
import arm.make_logic as make_logic
|
|
import arm.make_renderpath as make_renderpath
|
|
import arm.make_state as state
|
|
import arm.make_world as make_world
|
|
import arm.utils
|
|
import arm.write_data as write_data
|
|
|
|
scripts_mtime = 0 # Monitor source changes
|
|
profile_time = 0
|
|
|
|
def run_proc(cmd, done):
|
|
def fn(p, done):
|
|
p.wait()
|
|
if done != None:
|
|
done()
|
|
p = subprocess.Popen(cmd)
|
|
threading.Thread(target=fn, args=(p, done)).start()
|
|
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) as f:
|
|
json_file = f.read()
|
|
json_data = json.loads(json_file)
|
|
|
|
fp = arm.utils.get_fp_build()
|
|
arm.lib.make_datas.make(res, shader_name, json_data, fp, defs, make_variants)
|
|
|
|
path = fp + '/compiled/Shaders'
|
|
c = json_data['contexts'][0]
|
|
for s in ['vertex_shader', 'fragment_shader', 'geometry_shader', 'tesscontrol_shader', 'tesseval_shader']:
|
|
if s in c:
|
|
shutil.copy(c[s], path + '/' + c[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['Arm']
|
|
|
|
print('\n' + '_' * 10 + ' [Armory] Compiling ' + '_' * 10)
|
|
if wrd.arm_verbose_output:
|
|
print('\nArmory v{0} ({1})'.format(wrd.arm_version, wrd.arm_commit))
|
|
print('OS: ' + arm.utils.get_os() + ', Target: ' + state.target + ', GAPI: ' + arm.utils.get_gapi() + ', Blender: ' + bpy.app.version_string)
|
|
|
|
# Clean compiled variants if cache is disabled
|
|
build_dir = arm.utils.get_fp_build()
|
|
if not wrd.arm_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 + '/armory/Shaders/'
|
|
assets_path = sdk_path + '/armory/Assets/'
|
|
export_physics = bpy.data.worlds['Arm'].arm_physics != 'Disabled'
|
|
export_navigation = bpy.data.worlds['Arm'].arm_navigation != 'Disabled'
|
|
export_ui = bpy.data.worlds['Arm'].arm_ui != 'Disabled'
|
|
assets.reset()
|
|
|
|
# Build node trees
|
|
ArmoryExporter.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
|
|
ArmoryExporter.compress_enabled = state.is_publish and wrd.arm_asset_compression
|
|
ArmoryExporter.optimize_enabled = state.is_publish and wrd.arm_optimize_data
|
|
if not os.path.exists(build_dir + '/compiled/Assets'):
|
|
os.makedirs(build_dir + '/compiled/Assets')
|
|
# have a "zoo" collection in the current scene
|
|
export_coll = bpy.data.collections.new("export_coll")
|
|
bpy.context.scene.collection.children.link(export_coll)
|
|
for scene in bpy.data.scenes:
|
|
if scene == bpy.context.scene: continue
|
|
for o in scene.collection.all_objects:
|
|
if o.type == "MESH" or o.type == "EMPTY":
|
|
if o.name not in export_coll.all_objects.keys():
|
|
export_coll.objects.link(o)
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
bpy.data.collections.remove(export_coll) # destroy "zoo" collection
|
|
|
|
for scene in bpy.data.scenes:
|
|
if scene.arm_export:
|
|
ext = '.lz4' if ArmoryExporter.compress_enabled else '.arm'
|
|
asset_path = build_dir + '/compiled/Assets/' + arm.utils.safestr(scene.name) + ext
|
|
ArmoryExporter.export_scene(bpy.context, asset_path, scene=scene, depsgraph=depsgraph)
|
|
if ArmoryExporter.export_physics:
|
|
physics_found = True
|
|
if ArmoryExporter.export_navigation:
|
|
navigation_found = True
|
|
if ArmoryExporter.export_ui:
|
|
ui_found = True
|
|
assets.add(asset_path)
|
|
|
|
if physics_found == False: # Disable physics if no rigid body is exported
|
|
export_physics = False
|
|
|
|
if navigation_found == False:
|
|
export_navigation = False
|
|
|
|
if ui_found == False:
|
|
export_ui = False
|
|
|
|
if wrd.arm_ui == 'Enabled':
|
|
export_ui = True
|
|
|
|
modules = []
|
|
if wrd.arm_audio == 'Enabled':
|
|
modules.append('audio')
|
|
if export_physics:
|
|
modules.append('physics')
|
|
if export_navigation:
|
|
modules.append('navigation')
|
|
if export_ui:
|
|
modules.append('ui')
|
|
|
|
defs = arm.utils.def_strings_to_array(wrd.world_defs)
|
|
cdefs = arm.utils.def_strings_to_array(wrd.compo_defs)
|
|
|
|
if wrd.arm_verbose_output:
|
|
print('Exported modules:', modules)
|
|
print('Shader flags:', defs)
|
|
print('Compositor flags:', cdefs)
|
|
print('Khafile flags:', assets.khafile_defs)
|
|
|
|
# Render path is configurable at runtime
|
|
has_config = wrd.arm_write_config or os.path.exists(arm.utils.get_fp() + '/Bundled/config.arm')
|
|
|
|
# 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.arm') 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] = []
|
|
if ref.startswith('compositor_pass'):
|
|
compile_shader_pass(res, raw_shaders_path, ref, defs + cdefs, make_variants=has_config)
|
|
else:
|
|
compile_shader_pass(res, raw_shaders_path, ref, defs, make_variants=has_config)
|
|
|
|
# Workaround to also export non-material world shaders
|
|
res['shader_datas'] += make_world.shader_datas
|
|
|
|
arm.utils.write_arm(shaders_path + '/shader_datas.arm', res)
|
|
|
|
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.arm
|
|
resx, resy = arm.utils.get_render_resolution(arm.utils.get_active_scene())
|
|
if wrd.arm_write_config:
|
|
write_data.write_config(resx, resy)
|
|
|
|
# Change project version (Build, Publish)
|
|
if (not state.is_play) and (wrd.arm_project_version_autoinc):
|
|
wrd.arm_project_version = arm.utils.arm.utils.change_version_project(wrd.arm_project_version)
|
|
|
|
# Write khafile.js
|
|
enable_dce = state.is_publish and wrd.arm_dce
|
|
import_logic = not state.is_publish and arm.utils.logic_editor_space() != None
|
|
write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, state.is_publish, enable_dce, ArmoryExporter.import_traits, import_logic)
|
|
|
|
# Write Main.hx - depends on write_khafilejs for writing number of assets
|
|
scene_name = arm.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.arm_recompile = True
|
|
state.last_resx = resx
|
|
state.last_resy = resy
|
|
state.last_scene = scene_name
|
|
|
|
def compile(assets_only=False):
|
|
wrd = bpy.data.worlds['Arm']
|
|
fp = arm.utils.get_fp()
|
|
os.chdir(fp)
|
|
|
|
# Set build command
|
|
target_name = state.target
|
|
|
|
node_path = arm.utils.get_node_path()
|
|
khamake_path = arm.utils.get_khamake_path()
|
|
cmd = [node_path, khamake_path]
|
|
|
|
kha_target_name = arm.utils.get_kha_target(target_name)
|
|
if kha_target_name != '':
|
|
cmd.append(kha_target_name)
|
|
|
|
# Custom exporter
|
|
if state.is_export:
|
|
item = wrd.arm_exporterlist[wrd.arm_exporterlist_index]
|
|
if item.arm_project_target == 'custom' and item.arm_project_khamake != '':
|
|
for s in item.arm_project_khamake.split(' '):
|
|
cmd.append(s)
|
|
|
|
ffmpeg_path = arm.utils.get_ffmpeg_path() # Path to binary
|
|
if ffmpeg_path != '':
|
|
cmd.append('--ffmpeg')
|
|
cmd.append(ffmpeg_path) # '"' + ffmpeg_path + '"'
|
|
|
|
state.export_gapi = arm.utils.get_gapi()
|
|
cmd.append('-g')
|
|
cmd.append(state.export_gapi)
|
|
# Windows - Set Visual Studio Version
|
|
if state.target.startswith('windows'):
|
|
cmd.append('-visualstudio')
|
|
vs_ver, vs_year, vs_name, vs_id = arm.utils.get_visual_studio_from_version(wrd.arm_project_win_list_vs)
|
|
cmd.append(vs_id)
|
|
|
|
if arm.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 arm.utils.get_pref_or_default('khamake_debug', False):
|
|
cmd.append('--debug')
|
|
|
|
if arm.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 arm.utils.get_khamake_threads() > 1:
|
|
cmd.append('--parallelAssetConversion')
|
|
cmd.append(str(arm.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(arm.utils.build_dir() + '/debug')
|
|
# Start compilation server
|
|
if kha_target_name == 'krom' and arm.utils.get_compilation_server() and not assets_only and wrd.arm_cache_build:
|
|
compilation_server = True
|
|
arm.lib.server.run_haxe(arm.utils.get_haxe_path())
|
|
else:
|
|
cmd.append(arm.utils.build_dir())
|
|
|
|
if not wrd.arm_verbose_output:
|
|
cmd.append("--quiet")
|
|
else:
|
|
print("Using project from " + arm.utils.get_fp())
|
|
print("Running: ", *cmd)
|
|
|
|
#Project needs to be compiled at least once
|
|
#before compilation server can work
|
|
if not os.path.exists(arm.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')
|
|
state.proc_build = run_proc(cmd, assets_done if compilation_server else build_done)
|
|
|
|
def build(target, is_play=False, is_publish=False, is_export=False):
|
|
global profile_time
|
|
profile_time = time.time()
|
|
|
|
state.target = target
|
|
state.is_play = is_play
|
|
state.is_publish = is_publish
|
|
state.is_export = is_export
|
|
|
|
# Save blend
|
|
if arm.utils.get_save_on_build():
|
|
bpy.ops.wm.save_mainfile()
|
|
|
|
log.clear(clear_warnings=True)
|
|
|
|
# Set camera in active scene
|
|
active_scene = arm.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 = arm.utils.get_sdk_path()
|
|
raw_shaders_path = sdk_path + '/armory/Shaders/'
|
|
|
|
# Set dir
|
|
fp = arm.utils.get_fp()
|
|
os.chdir(fp)
|
|
|
|
# Create directories
|
|
wrd = bpy.data.worlds['Arm']
|
|
sources_path = 'Sources/' + arm.utils.safestr(wrd.arm_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
|
|
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/' + arm.utils.safestr(wrd.arm_project_package) + '/' + text.name, 'w') as f:
|
|
f.write(text.as_string())
|
|
|
|
# Export data
|
|
export_data(fp, sdk_path)
|
|
|
|
if state.target == 'html5':
|
|
w, h = arm.utils.get_render_resolution(arm.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, arm.utils.build_dir() + dest + os.path.basename(fn))
|
|
|
|
def play_done():
|
|
state.proc_play = None
|
|
state.redraw_ui = True
|
|
log.clear()
|
|
|
|
def assets_done():
|
|
if state.proc_build == None:
|
|
return
|
|
result = state.proc_build.poll()
|
|
if result == 0:
|
|
# Connect to the compilation server
|
|
os.chdir(arm.utils.build_dir() + '/debug/')
|
|
cmd = [arm.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():
|
|
print('Finished in ' + str(time.time() - profile_time))
|
|
if log.num_warnings > 0:
|
|
log.print_warn(f'{log.num_warnings} warnings 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['Arm'].arm_recompile = False
|
|
build_success()
|
|
else:
|
|
log.error('Build failed, check console')
|
|
|
|
def patch():
|
|
if state.proc_build != None:
|
|
return
|
|
assets.invalidate_enabled = False
|
|
fp = arm.utils.get_fp()
|
|
os.chdir(fp)
|
|
asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm'
|
|
ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene)
|
|
if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'):
|
|
raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/'
|
|
shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std')
|
|
node_path = arm.utils.get_node_path()
|
|
khamake_path = arm.utils.get_khamake_path()
|
|
|
|
cmd = [node_path, khamake_path, 'krom']
|
|
cmd.extend(('--shaderversion', '330', '--parallelAssetConversion', '4',
|
|
'--to', arm.utils.build_dir() + '/debug', '--nohaxe', '--noproject'))
|
|
|
|
assets.invalidate_enabled = True
|
|
state.proc_build = run_proc(cmd, patch_done)
|
|
|
|
def patch_done():
|
|
js = 'iron.Scene.patch();'
|
|
write_patch(js)
|
|
state.proc_build = None
|
|
|
|
patch_id = 0
|
|
|
|
def write_patch(js):
|
|
global patch_id
|
|
with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f:
|
|
patch_id += 1
|
|
f.write(str(patch_id) + '\n')
|
|
f.write(js)
|
|
|
|
def runtime_to_target():
|
|
wrd = bpy.data.worlds['Arm']
|
|
if wrd.arm_runtime == 'Krom':
|
|
return 'krom'
|
|
else:
|
|
return 'html5'
|
|
|
|
def get_khajs_path(target):
|
|
if target == 'krom':
|
|
return arm.utils.build_dir() + '/debug/krom/krom.js'
|
|
else: # Browser
|
|
return arm.utils.build_dir() + '/debug/html5/kha.js'
|
|
|
|
def play():
|
|
global scripts_mtime
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
build(target=runtime_to_target(), is_play=True)
|
|
|
|
khajs_path = get_khajs_path(state.target)
|
|
if not wrd.arm_cache_build or \
|
|
not os.path.isfile(khajs_path) or \
|
|
assets.khafile_defs_last != assets.khafile_defs or \
|
|
state.last_target != state.target:
|
|
wrd.arm_recompile = True
|
|
|
|
state.last_target = state.target
|
|
|
|
# Trait sources modified
|
|
state.mod_scripts = []
|
|
script_path = arm.utils.get_fp() + '/Sources/' + arm.utils.safestr(wrd.arm_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:
|
|
arm.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.arm_recompile = True
|
|
if new_mtime < mtime:
|
|
new_mtime = mtime
|
|
scripts_mtime = new_mtime
|
|
if len(state.mod_scripts) > 0: # Trait props
|
|
arm.utils.fetch_trait_props()
|
|
|
|
compile(assets_only=(not wrd.arm_recompile))
|
|
|
|
def build_success():
|
|
log.clear()
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
if state.is_play:
|
|
if wrd.arm_runtime == 'Browser':
|
|
# Start server
|
|
os.chdir(arm.utils.get_fp())
|
|
t = threading.Thread(name='localserver', target=arm.lib.server.run_tcp)
|
|
t.daemon = True
|
|
t.start()
|
|
html5_app_path = 'http://localhost:8040/' + arm.utils.build_dir() + '/debug/html5'
|
|
webbrowser.open(html5_app_path)
|
|
elif wrd.arm_runtime == 'Krom':
|
|
if wrd.arm_live_patch:
|
|
open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w').close()
|
|
krom_location, krom_path = arm.utils.krom_paths()
|
|
os.chdir(krom_location)
|
|
cmd = [krom_path, arm.utils.get_fp_build() + '/debug/krom', arm.utils.get_fp_build() + '/debug/krom-resources']
|
|
if arm.utils.get_os() == 'win':
|
|
cmd.append('--consolepid')
|
|
cmd.append(str(os.getpid()))
|
|
if wrd.arm_audio == 'Disabled':
|
|
cmd.append('--nosound')
|
|
if wrd.arm_verbose_output:
|
|
print("Running: ", *cmd)
|
|
state.proc_play = run_proc(cmd, play_done)
|
|
|
|
elif state.is_publish:
|
|
sdk_path = arm.utils.get_sdk_path()
|
|
target_name = arm.utils.get_kha_target(state.target)
|
|
files_path = os.path.join(arm.utils.get_fp_build(), target_name)
|
|
|
|
if (target_name == 'html5' or target_name == 'krom') and wrd.arm_minify_js:
|
|
# Minify JS
|
|
minifier_path = sdk_path + '/lib/armory_tools/uglifyjs/bin/uglifyjs'
|
|
if target_name == 'html5':
|
|
jsfile = files_path + '/kha.js'
|
|
else:
|
|
jsfile = files_path + '/krom.js'
|
|
args = [arm.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 = arm.utils.safestr(wrd.arm_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 = arm.utils.safestr(wrd.arm_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 = arm.utils.safestr(wrd.arm_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_ver, vs_year, vs_name, vs_id = arm.utils.get_visual_studio_from_version(wrd.arm_project_win_list_vs)
|
|
print('Exported '+ vs_name +' project to ' + project_path)
|
|
elif target_name.startswith('android'):
|
|
project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_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 arm.utils.get_arm_preferences().open_build_directory:
|
|
arm.utils.open_folder(project_path)
|
|
|
|
# Android build APK
|
|
if target_name.startswith('android'):
|
|
if (arm.utils.get_project_android_build_apk()) and (len(arm.utils.get_android_sdk_root_path()) > 0):
|
|
print("\nBuilding APK")
|
|
# Check settings
|
|
path_sdk = arm.utils.get_android_sdk_root_path()
|
|
if len(path_sdk) > 0:
|
|
# Check Environment Variables - ANDROID_SDK_ROOT
|
|
if os.getenv('ANDROID_SDK_ROOT') == 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 arm.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(arm.utils.get_html5_copy_path()) > 0 and (wrd.arm_project_html5_copy):
|
|
project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version)
|
|
dst = os.path.join(arm.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(arm.utils.get_link_web_server()) and (wrd.arm_project_html5_start_browser):
|
|
link_html5_app = arm.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'):
|
|
list_vs = []
|
|
err = ''
|
|
# Print message
|
|
project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version)
|
|
if int(wrd.arm_project_win_build) == 1:
|
|
print('\nOpen in Visual Studio ' + os.path.join(project_path, project_name + '.sln"'))
|
|
if int(wrd.arm_project_win_build) == 2:
|
|
print('\nCompile project ' + os.path.join(project_path, project_name + '.vcxproj'))
|
|
if int(wrd.arm_project_win_build) == 3:
|
|
print('\nCompile and run project ' + os.path.join(project_path, project_name + '.vcxproj'))
|
|
if int(wrd.arm_project_win_build) > 0:
|
|
# Check Visual Studio
|
|
list_vs, err = arm.utils.get_list_installed_vs(True, True, True)
|
|
if len(err) > 0:
|
|
print(err)
|
|
return
|
|
if len(list_vs) == 0:
|
|
print('No Visual Studio found')
|
|
return
|
|
is_check = False
|
|
for vs in list_vs:
|
|
if vs[0] == wrd.arm_project_win_list_vs:
|
|
is_check = True
|
|
break
|
|
if not is_check:
|
|
vs_ver, vs_year, vs_name, vs_id = arm.utils.get_visual_studio_from_version(wrd.arm_project_win_list_vs)
|
|
print(vs_name + ' not found.')
|
|
print('The following are installed on the PC:')
|
|
for vs in list_vs:
|
|
print('- ' + vs[1] + ' (version ' + vs[3] +')')
|
|
return
|
|
# Current VS
|
|
vs_path = ''
|
|
for vs in list_vs:
|
|
if vs[0] == wrd.arm_project_win_list_vs:
|
|
vs_path = vs[2]
|
|
break
|
|
# Open in Visual Studio
|
|
if int(wrd.arm_project_win_build) == 1:
|
|
cmd = os.path.join('start "' + vs_path, 'Common7', 'IDE', 'devenv.exe" "' + os.path.join(project_path, project_name + '.sln"'))
|
|
subprocess.Popen(cmd, shell=True)
|
|
# Compile
|
|
if int(wrd.arm_project_win_build) > 1:
|
|
bits = '64' if wrd.arm_project_win_build_arch == 'x64' else '32'
|
|
# vcvars
|
|
cmd = os.path.join(vs_path, 'VC', 'Auxiliary', 'Build', 'vcvars' + bits + '.bat')
|
|
if not os.path.isfile(cmd):
|
|
print('File "'+ cmd +'" not found. Verify ' + vs_name + ' was installed correctly')
|
|
log.error('Compile failed, check console')
|
|
return
|
|
state.proc_publish_build = run_proc(cmd, done_vs_vars)
|
|
|
|
def done_gradlew_build():
|
|
if state.proc_publish_build == None:
|
|
return
|
|
result = state.proc_publish_build.poll()
|
|
if result == 0:
|
|
state.proc_publish_build = None
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
path_apk = os.path.join(arm.utils.get_fp_build(), arm.utils.get_kha_target(state.target))
|
|
project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_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.arm_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.arm_project_android_copy_apk:
|
|
shutil.copyfile(file_name, os.path.join(arm.utils.get_android_apk_copy_path(), apk_name))
|
|
print("Copy APK to " + arm.utils.get_android_apk_copy_path())
|
|
# Open directory with APK
|
|
if arm.utils.get_android_open_build_apk_directory():
|
|
arm.utils.open_folder(path_apk)
|
|
# Open directory after copy APK
|
|
if arm.utils.get_android_apk_copy_open_directory():
|
|
arm.utils.open_folder(arm.utils.get_android_apk_copy_path())
|
|
# Running emulator
|
|
if wrd.arm_project_android_run_avd:
|
|
run_android_emulators(arm.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 = arm.utils.get_android_emulator_file()
|
|
if len(path_file) > 0:
|
|
if arm.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 == None:
|
|
return
|
|
result = state.proc_publish_build.poll()
|
|
if result == 0:
|
|
state.proc_publish_build = None
|
|
# MSBuild
|
|
wrd = bpy.data.worlds['Arm']
|
|
list_vs, err = arm.utils.get_list_installed_vs(True, True, True)
|
|
# Current VS
|
|
vs_path = ''
|
|
vs_name = ''
|
|
for vs in list_vs:
|
|
if vs[0] == wrd.arm_project_win_list_vs:
|
|
vs_name = vs[1]
|
|
vs_path = vs[2]
|
|
break
|
|
msbuild = os.path.join(vs_path, 'MSBuild', 'Current', 'Bin', 'MSBuild.exe')
|
|
if not os.path.isfile(msbuild):
|
|
print('File "'+ msbuild +'" not found. Verify ' + vs_name + ' was installed correctly')
|
|
log.error('Compile failed, check console')
|
|
state.redraw_ui = True
|
|
return
|
|
project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version)
|
|
project_path = os.path.join(arm.utils.get_fp_build(), arm.utils.get_kha_target(state.target)) + '-build'
|
|
cmd = '"' + msbuild + '" "' + os.path.join(project_path, project_name + '.vcxproj"')
|
|
# Arguments
|
|
platform = 'x64' if wrd.arm_project_win_build_arch == 'x64' else 'win32'
|
|
log_param = wrd.arm_project_win_build_log
|
|
if log_param == 'WarningsAndErrorsOnly':
|
|
log_param = 'WarningsOnly;ErrorsOnly'
|
|
cmd = cmd + ' -m:' + str(wrd.arm_project_win_build_cpu) + ' -clp:'+ log_param +' /p:Configuration='+ wrd.arm_project_win_build_mode +' /p:Platform=' + platform
|
|
print('\nCompiling the project ' + os.path.join(project_path, project_name + '.vcxproj"'))
|
|
state.proc_publish_build = run_proc(cmd, done_vs_build)
|
|
state.redraw_ui = True
|
|
else:
|
|
state.proc_publish_build = None
|
|
state.redraw_ui = True
|
|
log.error('\nCompile failed, check console')
|
|
|
|
def done_vs_build():
|
|
if state.proc_publish_build == None:
|
|
return
|
|
result = state.proc_publish_build.poll()
|
|
if result == 0:
|
|
state.proc_publish_build = None
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
project_path = os.path.join(arm.utils.get_fp_build(), arm.utils.get_kha_target(state.target)) + '-build'
|
|
if wrd.arm_project_win_build_arch == 'x64':
|
|
path = os.path.join(project_path, 'x64', wrd.arm_project_win_build_mode)
|
|
else:
|
|
path = os.path.join(project_path, wrd.arm_project_win_build_mode)
|
|
print('\nCompilation completed in ' + path)
|
|
# Run
|
|
if int(wrd.arm_project_win_build) == 3:
|
|
# Copying the executable file
|
|
res_path = os.path.join(arm.utils.get_fp_build(), arm.utils.get_kha_target(state.target))
|
|
file_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_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.arm_project_win_build_open:
|
|
arm.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(arm.utils.get_fp())
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
# Remove build and compiled data
|
|
try:
|
|
if os.path.isdir(arm.utils.build_dir()):
|
|
shutil.rmtree(arm.utils.build_dir(), onerror=remove_readonly)
|
|
if os.path.isdir(arm.utils.get_fp() + '/build'): # Kode Studio build dir
|
|
shutil.rmtree(arm.utils.get_fp() + '/build', onerror=remove_readonly)
|
|
except:
|
|
print('Armory Warning: Some files in the build folder are locked')
|
|
|
|
# Remove compiled nodes
|
|
pkg_dir = arm.utils.safestr(wrd.arm_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)
|
|
|
|
# To recache signatures for batched materials
|
|
for mat in bpy.data.materials:
|
|
mat.signature = ''
|
|
mat.arm_cached = False
|
|
|
|
# Restart compilation server
|
|
if arm.utils.get_compilation_server():
|
|
arm.lib.server.kill_haxe()
|
|
|
|
print('Project cleaned')
|