armory/blender/make.py

506 lines
18 KiB
Python
Raw Normal View History

import os
2017-01-18 14:52:51 +01:00
import glob
import time
import shutil
import bpy
import json
from bpy.props import *
import subprocess
import threading
import webbrowser
import write_data
2016-09-23 00:34:42 +02:00
import make_logic
import make_renderpath
import make_world
2016-10-19 13:28:06 +02:00
import make_utils
import make_state as state
import path_tracer
from exporter import ArmoryExporter
2016-10-27 01:11:11 +02:00
import armutils
import assets
2016-10-19 13:28:06 +02:00
import log
import lib.make_datas
import lib.make_variants
import lib.server
exporter = ArmoryExporter()
2017-01-18 14:52:51 +01:00
scripts_mtime = 0 # Monitor source changes
2016-10-17 00:02:51 +02:00
def compile_shader(raw_shaders_path, shader_name, defs):
os.chdir(raw_shaders_path + './' + shader_name)
2016-10-27 01:11:11 +02:00
fp = armutils.get_fp()
2016-10-17 00:02:51 +02:00
# Open json file
2016-12-13 01:09:17 +01:00
json_name = shader_name + '.json'
2016-10-17 00:02:51 +02:00
base_name = json_name.split('.', 1)[0]
2016-10-25 16:15:07 +02:00
with open(json_name) as f:
json_file = f.read()
2016-10-17 00:02:51 +02:00
json_data = json.loads(json_file)
lib.make_datas.make(base_name, json_data, fp, defs)
lib.make_variants.make(base_name, json_data, fp, defs)
2016-10-12 17:52:27 +02:00
def export_data(fp, sdk_path, is_play=False, is_publish=False, in_viewport=False):
2016-10-19 13:28:06 +02:00
global exporter
2016-12-10 14:26:02 +01:00
wrd = bpy.data.worlds['Arm']
print('\nArmory v' + wrd.arm_version)
2016-10-19 13:28:06 +02:00
2016-12-13 01:09:17 +01:00
# Clean compiled variants if cache is disabled
if wrd.arm_cache_shaders == False:
if os.path.isdir('build/html5-resources'):
shutil.rmtree('build/html5-resources')
if os.path.isdir('build/krom-resources'):
shutil.rmtree('build/krom-resources')
if os.path.isdir('build/window/krom-resources'):
shutil.rmtree('build/window/krom-resources')
if os.path.isdir('build/compiled/Shaders'):
shutil.rmtree('build/compiled/Shaders')
if os.path.isdir('build/compiled/ShaderDatas'):
shutil.rmtree('build/compiled/ShaderDatas')
if os.path.isdir('build/compiled/ShaderRaws'):
shutil.rmtree('build/compiled/ShaderRaws')
# Remove shader datas if shaders were deleted
elif os.path.isdir('build/compiled/Shaders') == False and os.path.isdir('build/compiled/ShaderDatas') == True:
shutil.rmtree('build/compiled/ShaderDatas')
2016-10-17 00:02:51 +02:00
raw_shaders_path = sdk_path + 'armory/Shaders/'
assets_path = sdk_path + 'armory/Assets/'
2016-10-17 00:02:51 +02:00
export_physics = bpy.data.worlds['Arm'].arm_physics != 'Disabled'
2016-12-07 10:37:08 +01:00
export_navigation = bpy.data.worlds['Arm'].arm_navigation != 'Disabled'
assets.reset()
# Build node trees
# TODO: cache
2016-10-19 13:28:06 +02:00
make_logic.build_node_trees()
active_worlds = set()
for scene in bpy.data.scenes:
2016-11-21 17:15:13 +01:00
if scene.game_export and scene.world != None:
active_worlds.add(scene.world)
2016-10-19 13:28:06 +02:00
world_outputs = make_world.build_node_trees(active_worlds)
make_renderpath.build_node_trees(assets_path)
for wout in world_outputs:
make_world.write_output(wout)
# Export scene data
2016-09-14 11:49:32 +02:00
assets.embedded_data = sorted(list(set(assets.embedded_data)))
2016-09-12 02:24:20 +02:00
physics_found = False
2016-12-07 10:37:08 +01:00
navigation_found = False
2016-10-15 12:17:33 +02:00
ArmoryExporter.compress_enabled = is_publish
2016-11-05 14:12:36 +01:00
ArmoryExporter.in_viewport = in_viewport
for scene in bpy.data.scenes:
if scene.game_export:
2016-10-17 00:02:51 +02:00
ext = '.zip' if (scene.data_compressed and is_publish) else '.arm'
2016-10-27 01:11:11 +02:00
asset_path = 'build/compiled/Assets/' + armutils.safe_filename(scene.name) + ext
2016-10-19 13:28:06 +02:00
exporter.execute(bpy.context, asset_path)
2016-09-12 02:24:20 +02:00
if physics_found == False and ArmoryExporter.export_physics:
physics_found = True
2016-12-07 10:37:08 +01:00
if navigation_found == False and ArmoryExporter.export_navigation:
navigation_found = True
assets.add(asset_path)
2016-09-12 02:24:20 +02:00
if physics_found == False: # Disable physics anyway if no rigid body exported
export_physics = False
2016-12-07 10:37:08 +01:00
if navigation_found == False:
export_navigation = False
2016-10-17 02:29:37 +02:00
# Write referenced shader variants
for ref in assets.shader_datas:
# Data does not exist yet
if not os.path.isfile(fp + '/' + ref):
shader_name = ref.split('/')[3] # Extract from 'build/compiled/...'
2017-01-14 12:44:43 +01:00
defs = make_utils.def_strings_to_array(wrd.world_defs + wrd.rp_defs)
2016-12-20 01:39:16 +01:00
if shader_name.startswith('compositor_pass'):
defs += make_utils.def_strings_to_array(wrd.compo_defs)
2016-10-17 00:02:51 +02:00
compile_shader(raw_shaders_path, shader_name, defs)
2016-10-17 02:29:37 +02:00
# Reset path
os.chdir(fp)
2016-10-17 02:29:37 +02:00
# Copy std shaders
if not os.path.isdir('build/compiled/Shaders/std'):
shutil.copytree(raw_shaders_path + 'std', 'build/compiled/Shaders/std')
# Write compiled.glsl
write_data.write_compiledglsl()
# Write khafile.js
2016-12-07 10:37:08 +01:00
write_data.write_khafilejs(is_play, export_physics, export_navigation, dce_full=is_publish)
2016-11-07 23:06:08 +01:00
# Write Main.hx - depends on write_khafilejs for writing number of assets
2016-10-17 00:02:51 +02:00
write_data.write_main(is_play, in_viewport, is_publish)
2016-11-26 23:05:26 +01:00
def compile_project(target_name=None, is_publish=False, watch=False, patch=False):
2016-10-27 01:11:11 +02:00
ffmpeg_path = armutils.get_ffmpeg_path()
2016-10-12 17:52:27 +02:00
wrd = bpy.data.worlds['Arm']
# Set build command
if target_name == None:
2016-10-17 00:02:51 +02:00
target_name = wrd.arm_project_target
2017-01-17 18:13:54 +01:00
elif target_name == 'native':
target_name = ''
2017-02-09 00:33:19 +01:00
node_path = armutils.get_node_path()
khamake_path = armutils.get_khamake_path()
2016-10-19 13:28:06 +02:00
kha_target_name = make_utils.get_kha_target(target_name)
cmd = [node_path, khamake_path, kha_target_name]
2016-09-03 13:30:52 +02:00
2016-09-23 00:34:42 +02:00
if ffmpeg_path != '':
cmd.append('--ffmpeg')
cmd.append('"' + ffmpeg_path + '"')
2017-02-07 12:22:38 +01:00
if target_name == '' or target_name == '--run':
2016-10-09 16:06:18 +02:00
cmd.append('-g')
2017-02-07 12:22:38 +01:00
cmd.append(getattr(wrd, 'arm_gapi_' + armutils.get_os()))
2016-10-09 16:06:18 +02:00
2016-11-24 17:35:12 +01:00
if kha_target_name == 'krom':
2017-01-18 14:52:51 +01:00
if state.in_viewport:
2016-11-24 17:35:12 +01:00
if armutils.glsl_version() >= 330:
cmd.append('--shaderversion')
cmd.append('330')
else:
cmd.append('--shaderversion')
cmd.append('110')
else:
cmd.append('--to')
cmd.append('build/window')
2016-10-12 17:52:27 +02:00
# User defined commands
2016-10-17 00:02:51 +02:00
cmd_text = wrd.arm_command_line
2016-10-12 17:52:27 +02:00
if cmd_text != '':
for s in bpy.data.texts[cmd_text].as_string().split(' '):
cmd.append(s)
2017-01-18 14:52:51 +01:00
if patch:
2017-01-23 20:41:45 +01:00
if state.compileproc == None:
# Quick build - disable krafix and haxe
cmd.append('--nohaxe')
cmd.append('--noshaders')
2017-01-23 00:48:59 +01:00
cmd.append('--noproject')
2017-01-23 20:41:45 +01:00
state.compileproc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
2017-01-18 14:52:51 +01:00
if state.playproc == None:
if state.in_viewport:
mode = 'play_viewport'
else:
mode = 'play'
else:
mode = 'build'
threading.Timer(0.1, watch_patch, [mode]).start()
2016-10-19 13:28:06 +02:00
return state.compileproc
2017-01-18 14:52:51 +01:00
elif watch:
2016-10-19 13:28:06 +02:00
state.compileproc = subprocess.Popen(cmd)
2016-09-23 00:34:42 +02:00
threading.Timer(0.1, watch_compile, ['build']).start()
2016-10-19 13:28:06 +02:00
return state.compileproc
2016-09-12 20:12:13 +02:00
else:
return subprocess.Popen(cmd)
2016-11-30 11:40:28 +01:00
def build_project(is_play=False, is_publish=False, in_viewport=False, target=None):
wrd = bpy.data.worlds['Arm']
# Set target
if target == None:
state.target = wrd.arm_project_target.lower()
# Clear flag
2016-10-19 13:28:06 +02:00
state.in_viewport = False
# Save blend
bpy.ops.wm.save_mainfile()
2016-10-19 13:28:06 +02:00
log.clear()
2016-09-23 00:34:42 +02:00
2016-09-08 14:08:31 +02:00
# Set camera in active scene
2016-10-17 00:02:51 +02:00
active_scene = bpy.context.screen.scene if wrd.arm_play_active_scene else bpy.data.scenes[wrd.arm_project_scene]
2016-09-08 14:08:31 +02:00
if active_scene.camera == None:
for o in active_scene.objects:
if o.type == 'CAMERA':
active_scene.camera = o
break
# Get paths
2016-10-27 01:11:11 +02:00
sdk_path = armutils.get_sdk_path()
2016-10-17 00:02:51 +02:00
raw_shaders_path = sdk_path + '/armory/Shaders/'
# Set dir
2016-10-27 01:11:11 +02:00
fp = armutils.get_fp()
os.chdir(fp)
# Create directories
2017-02-09 23:33:54 +01:00
sources_path = 'Sources/' + wrd.arm_project_package
if not os.path.exists(sources_path):
os.makedirs(sources_path)
# Compile path tracer shaders
if len(bpy.data.cameras) > 0 and bpy.data.cameras[0].renderpath_path == 'pathtrace_path':
2016-10-17 00:02:51 +02:00
path_tracer.compile(raw_shaders_path + 'pt_trace_pass/pt_trace_pass.frag.glsl')
# 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:
2017-01-10 22:57:22 +01:00
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':
2016-10-17 00:02:51 +02:00
with open('Sources/' + bpy.data.worlds['Arm'].arm_project_package + '/' + text.name, 'w') as f:
f.write(text.as_string())
# Export data
2016-10-12 17:52:27 +02:00
export_data(fp, sdk_path, is_play=is_play, is_publish=is_publish, in_viewport=in_viewport)
2016-10-19 13:28:06 +02:00
if state.playproc == None:
log.print_progress(50)
2016-09-23 00:34:42 +02:00
def stop_project():
2016-10-19 13:28:06 +02:00
if state.playproc != None:
state.playproc.terminate()
state.playproc = None
def watch_play():
2016-10-19 13:28:06 +02:00
if state.playproc == None:
return
line = b''
2016-10-19 13:28:06 +02:00
while state.playproc != None and state.playproc.poll() == None:
char = state.playproc.stderr.read(1) # Read immediately one by one
if char == b'\n':
2016-09-08 14:08:31 +02:00
msg = str(line).split('"', 1) # Extract message
if len(msg) > 1:
trace = msg[1].rsplit('"', 1)[0]
2016-11-12 18:30:39 +01:00
log.electron_trace(trace)
line = b''
else:
line += char
2016-10-19 13:28:06 +02:00
state.playproc = None
state.playproc_finished = True
log.clear()
2016-09-23 00:34:42 +02:00
def watch_compile(mode):
2016-10-19 13:28:06 +02:00
state.compileproc.wait()
log.print_progress(100)
if state.compileproc == None: ##
2016-10-15 12:17:33 +02:00
return
2016-10-19 13:28:06 +02:00
result = state.compileproc.poll()
state.compileproc = None
state.compileproc_finished = True
if result == 0:
2017-01-18 14:52:51 +01:00
bpy.data.worlds['Arm'].arm_recompile = False
2016-10-19 13:28:06 +02:00
state.compileproc_success = True
2016-09-23 00:34:42 +02:00
on_compiled(mode)
else:
2016-10-19 13:28:06 +02:00
state.compileproc_success = False
log.print_info('Build failed, check console')
2017-01-18 14:52:51 +01:00
def watch_patch(mode):
2016-10-19 13:28:06 +02:00
state.compileproc.wait()
2017-01-18 14:52:51 +01:00
log.print_progress(100)
2016-10-19 13:28:06 +02:00
# result = state.compileproc.poll()
state.compileproc = None
state.compileproc_finished = True
2017-01-18 14:52:51 +01:00
on_compiled(mode)
2016-09-12 20:12:13 +02:00
2017-01-17 18:13:54 +01:00
def runtime_to_target(in_viewport):
wrd = bpy.data.worlds['Arm']
if in_viewport or wrd.arm_play_runtime == 'Krom':
return 'krom'
elif wrd.arm_play_runtime == 'Native':
return 'native'
else:
return 'html5'
2017-01-18 14:52:51 +01:00
def get_khajs_path(in_viewport, target):
if in_viewport:
return 'build/krom/krom.js'
elif target == 'krom':
return 'build/window/krom/krom.js'
else: # browser, electron
return 'build/html5/kha.js'
def play_project(in_viewport):
global scripts_mtime
2016-11-30 11:40:28 +01:00
wrd = bpy.data.worlds['Arm']
# Store area
2017-01-18 14:52:51 +01:00
if armutils.with_krom() and in_viewport and bpy.context.area != None and bpy.context.area.type == 'VIEW_3D':
2016-10-19 13:28:06 +02:00
state.play_area = bpy.context.area
2016-09-12 02:24:20 +02:00
2017-01-17 18:13:54 +01:00
state.target = runtime_to_target(in_viewport)
2016-11-30 11:40:28 +01:00
# Build data
2016-11-30 11:40:28 +01:00
build_project(is_play=True, in_viewport=in_viewport, target=state.target)
2016-10-19 13:28:06 +02:00
state.in_viewport = in_viewport
2017-01-18 14:52:51 +01:00
khajs_path = get_khajs_path(in_viewport, state.target)
if wrd.arm_recompile or \
wrd.arm_recompile_trigger or \
not wrd.arm_cache_compiler or \
not wrd.arm_cache_shaders or \
not os.path.isfile(khajs_path) or \
2017-01-29 16:15:04 +01:00
state.last_target != state.target or \
state.last_in_viewport != state.in_viewport:
2017-01-18 14:52:51 +01:00
wrd.arm_recompile = True
wrd.arm_recompile_trigger = False
state.last_target = state.target
2017-01-29 16:15:04 +01:00
state.last_in_viewport = state.in_viewport
2017-01-18 14:52:51 +01:00
# Trait sources modified
script_path = armutils.get_fp() + '/Sources/' + wrd.arm_project_package
if os.path.isdir(script_path):
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
scripts_mtime = mtime
wrd.arm_recompile = True
# New compile requred - traits or materials changed
2017-01-29 16:15:04 +01:00
if wrd.arm_recompile or state.target == 'native':
2017-01-18 14:52:51 +01:00
# Unable to live-patch, stop player
if state.krom_running:
bpy.ops.arm.space_stop()
# play_project(in_viewport=True) # Restart
return
mode = 'play'
if state.target == 'native':
state.compileproc = compile_project(target_name='--run')
elif state.target == 'krom':
if in_viewport:
mode = 'play_viewport'
state.compileproc = compile_project(target_name='krom')
else: # Electron, Browser
2017-01-19 00:26:18 +01:00
w, h = armutils.get_render_resolution(armutils.get_active_scene())
2017-01-18 14:52:51 +01:00
write_data.write_electronjs(w, h)
write_data.write_indexhtml(w, h)
state.compileproc = compile_project(target_name='html5')
threading.Timer(0.1, watch_compile, [mode]).start()
else: # kha.js up to date
compile_project(target_name=state.target, patch=True)
def on_compiled(mode): # build, play, play_viewport, publish
2016-10-19 13:28:06 +02:00
log.clear()
2016-10-27 01:11:11 +02:00
sdk_path = armutils.get_sdk_path()
2017-01-30 12:02:40 +01:00
wrd = bpy.data.worlds['Arm']
2016-09-08 14:08:31 +02:00
# Print info
2016-09-23 00:34:42 +02:00
if mode == 'publish':
2017-01-30 12:02:40 +01:00
target_name = make_utils.get_kha_target(wrd.arm_project_target)
2016-09-08 14:08:31 +02:00
print('Project published')
2016-10-27 01:11:11 +02:00
files_path = armutils.get_fp() + '/build/' + target_name
2016-09-08 14:08:31 +02:00
if target_name == 'html5':
print('HTML5 files are located in ' + files_path)
elif target_name == 'ios' or target_name == 'osx': # TODO: to macos
print('XCode project files are located in ' + files_path + '-build')
elif target_name == 'windows':
print('VisualStudio 2015 project files are located in ' + files_path + '-build')
elif target_name == 'android-native':
2017-01-30 12:02:40 +01:00
print('Android Studio project files are located in ' + files_path + '-build/' + wrd.arm_project_name)
2016-09-08 14:08:31 +02:00
else:
print('Makefiles are located in ' + files_path + '-build')
return
2016-11-23 15:34:59 +01:00
# Launch project in new window
elif mode =='play':
2016-10-17 00:02:51 +02:00
if wrd.arm_play_runtime == 'Electron':
2016-09-12 02:24:20 +02:00
electron_app_path = './build/electron.js'
2016-10-27 01:11:11 +02:00
if armutils.get_os() == 'win':
2016-11-01 12:02:58 +01:00
electron_path = sdk_path + 'win32/Kode Studio.exe'
2016-10-27 01:11:11 +02:00
elif armutils.get_os() == 'mac':
2016-11-13 11:46:54 +01:00
electron_path = sdk_path + 'Kode Studio.app/Contents/MacOS/Electron'
2016-09-12 02:24:20 +02:00
else:
2016-11-01 12:02:58 +01:00
electron_path = sdk_path + 'linux64/kodestudio'
2016-09-12 02:24:20 +02:00
2016-10-19 13:28:06 +02:00
state.playproc = subprocess.Popen([electron_path, '--chromedebug', '--remote-debugging-port=9222', '--enable-logging', electron_app_path], stderr=subprocess.PIPE)
2016-09-12 02:24:20 +02:00
watch_play()
2016-10-17 00:02:51 +02:00
elif wrd.arm_play_runtime == 'Browser':
2016-09-12 02:24:20 +02:00
# Start server
2016-10-27 01:11:11 +02:00
os.chdir(armutils.get_fp())
2016-10-19 13:28:06 +02:00
t = threading.Thread(name='localserver', target=lib.server.run)
2016-09-12 02:24:20 +02:00
t.daemon = True
t.start()
html5_app_path = 'http://localhost:8040/build/html5'
webbrowser.open(html5_app_path)
2016-11-24 17:35:12 +01:00
elif wrd.arm_play_runtime == 'Krom':
if armutils.get_os() == 'win':
2016-12-21 00:51:04 +01:00
krom_location = sdk_path + '/win32/Krom/win32'
2016-11-26 12:17:33 +01:00
krom_path = krom_location + '/Krom.exe'
2016-11-24 17:35:12 +01:00
elif armutils.get_os() == 'mac':
2016-12-21 00:51:04 +01:00
krom_location = sdk_path + '/Kode Studio.app/Contents/Krom/macos/Krom.app/Contents/MacOS'
2016-11-26 12:17:33 +01:00
krom_path = krom_location + '/Krom'
2016-11-24 17:35:12 +01:00
else:
2016-12-21 00:51:04 +01:00
krom_location = sdk_path + '/linux64/Krom/linux'
2016-11-25 20:52:25 +01:00
krom_path = krom_location + '/Krom'
2016-11-26 12:17:33 +01:00
os.chdir(krom_location)
2016-11-24 17:35:12 +01:00
state.playproc = subprocess.Popen([krom_path, armutils.get_fp() + '/build/window/krom', armutils.get_fp() + '/build/window/krom-resources'], stderr=subprocess.PIPE)
watch_play()
2016-11-05 14:12:36 +01:00
def clean_cache():
os.chdir(armutils.get_fp())
wrd = bpy.data.worlds['Arm']
# Preserve envmaps
envmaps_path = 'build/compiled/Assets/envmaps'
if os.path.isdir(envmaps_path):
shutil.move(envmaps_path, '.')
# Remove compiled data
if os.path.isdir('build/compiled'):
shutil.rmtree('build/compiled')
# Move envmaps back
if os.path.isdir('envmaps'):
os.makedirs('build/compiled/Assets')
shutil.move('envmaps', 'build/compiled/Assets')
def clean_project():
2016-10-27 01:11:11 +02:00
os.chdir(armutils.get_fp())
2016-09-23 00:34:42 +02:00
wrd = bpy.data.worlds['Arm']
# Remove build and compiled data
if os.path.isdir('build'):
shutil.rmtree('build')
# Remove compiled nodes
2016-10-17 00:02:51 +02:00
nodes_path = 'Sources/' + wrd.arm_project_package.replace('.', '/') + '/node/'
if os.path.isdir(nodes_path):
shutil.rmtree(nodes_path)
# Remove khafile/korefile/Main.hx
if os.path.isfile('khafile.js'):
os.remove('khafile.js')
if os.path.isfile('korefile.js'):
os.remove('korefile.js')
if os.path.isfile('Sources/Main.hx'):
os.remove('Sources/Main.hx')
print('Project cleaned')
2016-09-08 14:08:31 +02:00
def publish_project():
2016-09-23 00:34:42 +02:00
# Force minimize data
2016-10-19 13:28:06 +02:00
assets.invalidate_enabled = False
2016-10-17 00:02:51 +02:00
minimize = bpy.data.worlds['Arm'].arm_minimize
bpy.data.worlds['Arm'].arm_minimize = True
2016-09-08 14:08:31 +02:00
clean_project()
2016-10-09 16:06:18 +02:00
build_project(is_publish=True)
2016-11-30 11:40:28 +01:00
state.compileproc = compile_project(target_name=bpy.data.worlds['Arm'].arm_project_target, is_publish=True)
2016-09-23 00:34:42 +02:00
threading.Timer(0.1, watch_compile, ['publish']).start()
2016-10-17 00:02:51 +02:00
bpy.data.worlds['Arm'].arm_minimize = minimize
2016-10-19 13:28:06 +02:00
assets.invalidate_enabled = True
2016-11-12 18:30:39 +01:00
def get_render_result():
with open(armutils.get_fp() + '/build/html5/render.msg', 'w') as f:
pass