armory/blender/arm/utils.py

397 lines
13 KiB
Python
Raw Normal View History

2016-06-30 13:22:05 +02:00
import bpy
import json
import os
2016-07-10 00:51:39 +02:00
import glob
2016-08-12 02:29:09 +02:00
import platform
2016-10-15 12:17:33 +02:00
import zipfile
2016-11-21 16:49:32 +01:00
import re
2017-05-17 17:06:52 +02:00
import arm.lib.armpack
2017-08-21 15:36:21 +02:00
import arm.make_state as state
import arm.make_utils as make_utils
2016-06-30 13:22:05 +02:00
2016-07-20 17:33:17 +02:00
def write_arm(filepath, output):
2016-10-15 12:17:33 +02:00
if filepath.endswith('.zip'):
with zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED) as zip_file:
2016-10-17 00:02:51 +02:00
if bpy.data.worlds['Arm'].arm_minimize:
2017-05-17 17:06:52 +02:00
zip_file.writestr('data.arm', arm.lib.armpack.packb(output))
2016-10-15 12:17:33 +02:00
else:
zip_file.writestr('data.arm', json.dumps(output, sort_keys=True, indent=4))
2016-07-20 17:33:17 +02:00
else:
2016-10-17 00:02:51 +02:00
if bpy.data.worlds['Arm'].arm_minimize:
2016-10-15 12:17:33 +02:00
with open(filepath, 'wb') as f:
2017-05-17 17:06:52 +02:00
f.write(arm.lib.armpack.packb(output))
2016-10-15 12:17:33 +02:00
else:
with open(filepath, 'w') as f:
f.write(json.dumps(output, sort_keys=True, indent=4))
2016-06-30 13:22:05 +02:00
def write_image(image, path, file_format='JPEG'):
# Convert image to compatible format
print('Armory Info: Writing ' + path)
ren = bpy.context.scene.render
orig_quality = ren.image_settings.quality
orig_file_format = ren.image_settings.file_format
ren.image_settings.quality = 90
ren.image_settings.file_format = file_format
image.save_render(path, bpy.context.scene)
ren.image_settings.quality = orig_quality
ren.image_settings.file_format = orig_file_format
2017-05-23 01:03:44 +02:00
def blend_name():
return bpy.path.basename(bpy.context.blend_data.filepath).rsplit('.')[0]
def build_dir():
return 'build_' + safestr(blend_name())
2016-06-30 13:22:05 +02:00
def get_fp():
2017-10-06 18:56:05 +02:00
wrd = bpy.data.worlds['Arm']
if wrd.arm_project_root != '':
return bpy.path.abspath(wrd.arm_project_root)
else:
s = bpy.data.filepath.split(os.path.sep)
s.pop()
return os.path.sep.join(s)
2016-06-30 13:22:05 +02:00
2017-05-23 01:03:44 +02:00
def get_fp_build():
return get_fp() + '/' + build_dir()
2016-08-12 02:29:09 +02:00
def get_os():
s = platform.system()
if s == 'Windows':
return 'win'
elif s == 'Darwin':
return 'mac'
else:
return 'linux'
2017-04-19 11:48:30 +02:00
def get_gapi():
2017-08-21 15:36:21 +02:00
wrd = bpy.data.worlds['Arm']
if state.is_export:
item = wrd.arm_exporterlist[wrd.arm_exporterlist_index]
return getattr(item, make_utils.target_to_gapi(item.arm_project_target))
else:
if wrd.arm_play_runtime == 'Browser':
return 'webgl'
else:
return 'opengl'
2017-04-19 11:48:30 +02:00
2017-08-21 20:16:06 +02:00
def get_rp():
wrd = bpy.data.worlds['Arm']
return wrd.arm_rplist[wrd.arm_rplist_index]
def get_sdk_path():
2016-07-10 00:51:39 +02:00
user_preferences = bpy.context.user_preferences
addon_prefs = user_preferences.addons['armory'].preferences
2016-11-23 15:34:59 +01:00
if with_krom() and addon_prefs.sdk_bundled:
if get_os() == 'mac':
2016-12-05 17:37:26 +01:00
# SDK on MacOS is located in .app folder due to security
2017-01-28 20:00:04 +01:00
p = bpy.app.binary_path
if p.endswith('Contents/MacOS/blender'):
return p[:-len('Contents/MacOS/blender')] + '/armsdk/'
else:
return p[:-len('Contents/MacOS/./blender')] + '/armsdk/'
2016-11-02 15:51:25 +01:00
elif get_os() == 'linux':
2016-12-21 00:51:04 +01:00
# /blender
return bpy.app.binary_path.rsplit('/', 1)[0] + '/armsdk/'
else:
2016-12-21 00:51:04 +01:00
# /blender.exe
return bpy.app.binary_path.replace('\\', '/').rsplit('/', 1)[0] + '/armsdk/'
else:
return addon_prefs.sdk_path
def get_ffmpeg_path():
user_preferences = bpy.context.user_preferences
addon_prefs = user_preferences.addons['armory'].preferences
return addon_prefs.ffmpeg_path
2017-04-11 23:21:42 +02:00
def get_ease_viewport_camera():
2017-07-07 17:23:32 +02:00
return True
2017-04-11 23:21:42 +02:00
2017-02-22 16:14:55 +01:00
def get_save_on_build():
user_preferences = bpy.context.user_preferences
addon_prefs = user_preferences.addons['armory'].preferences
return True if not hasattr(addon_prefs, 'save_on_build') else addon_prefs.save_on_build
2017-02-09 00:33:19 +01:00
def get_node_path():
if get_os() == 'win':
return get_sdk_path() + '/nodejs/node.exe'
elif get_os() == 'mac':
return get_sdk_path() + '/nodejs/node-osx'
else:
return get_sdk_path() + '/nodejs/node-linux64'
2017-03-20 13:23:31 +01:00
def get_kha_path():
2017-02-09 00:33:19 +01:00
if os.path.exists('Kha'):
2017-03-20 13:23:31 +01:00
return 'Kha'
2017-02-09 00:33:19 +01:00
if get_os() == 'win':
2017-03-20 13:23:31 +01:00
return get_sdk_path() + '/win32/Kha'
2017-02-09 00:33:19 +01:00
elif get_os() == 'mac':
2017-03-20 13:23:31 +01:00
return get_sdk_path() + '/Kode Studio.app/Contents/Kha'
2017-02-09 00:33:19 +01:00
else:
2017-03-20 13:23:31 +01:00
return get_sdk_path() + '/linux64/Kha'
2017-07-02 20:48:19 +02:00
def get_haxe_path():
if get_os() == 'win':
return get_sdk_path() + '/win32/Kha/Tools/haxe/haxe.exe'
elif get_os() == 'mac':
return get_sdk_path() + '/Kode Studio.app/Contents/Kha/Tools/haxe/haxe-osx'
else:
return get_sdk_path() + '/linux64/Kha/Tools/haxe/haxe-linux64'
2017-03-20 13:23:31 +01:00
def get_khamake_path():
return get_kha_path() + '/make'
2017-02-09 00:33:19 +01:00
2017-06-05 02:32:51 +02:00
def krom_paths():
sdk_path = get_sdk_path()
if arm.utils.get_os() == 'win':
krom_location = sdk_path + '/win32/Krom/win32'
krom_path = krom_location + '/Krom.exe'
elif arm.utils.get_os() == 'mac':
krom_location = sdk_path + '/Kode Studio.app/Contents/Krom/macos/Krom.app/Contents/MacOS'
krom_path = krom_location + '/Krom'
else:
krom_location = sdk_path + '/linux64/Krom/linux'
krom_path = krom_location + '/Krom'
return krom_location, krom_path
2017-02-22 17:32:34 +01:00
def fetch_bundled_script_names():
wrd = bpy.data.worlds['Arm']
2017-08-21 12:17:55 +02:00
wrd.arm_bundled_scripts_list.clear()
2017-02-09 00:33:19 +01:00
os.chdir(get_sdk_path() + '/armory/Sources/armory/trait')
2016-07-10 00:51:39 +02:00
for file in glob.glob('*.hx'):
2017-08-21 12:17:55 +02:00
wrd.arm_bundled_scripts_list.add().name = file.rsplit('.')[0]
2017-02-22 17:32:34 +01:00
2017-07-24 02:27:22 +02:00
script_props = {}
2017-08-08 19:56:47 +02:00
script_props_defaults = {}
2017-07-24 02:27:22 +02:00
def fetch_script_props(file):
with open(file) as f:
if '/' in file:
file = file.split('/')[-1]
if '\\' in file:
file = file.split('\\')[-1]
name = file.rsplit('.')[0]
script_props[name] = []
2017-08-08 19:56:47 +02:00
script_props_defaults[name] = []
2017-07-24 02:27:22 +02:00
lines = f.read().splitlines()
readprop = False
for l in lines:
if readprop:
p = l.split('var ')[1]
if ':' in p:
2017-08-08 19:56:47 +02:00
if '=' in p: # Fetch default value
s = p.split('=')
v = s[1].split(';')[0].strip()
else:
v = ''
2017-07-24 02:27:22 +02:00
p = p.split(':')[0].strip()
script_props[name].append(p)
2017-08-08 19:56:47 +02:00
script_props_defaults[name].append(v)
2017-07-24 02:27:22 +02:00
elif '=' in p:
2017-08-08 19:56:47 +02:00
s = p.split('=')
p = s[0].strip()
v = s[1].split(';')[0].strip()
2017-07-24 02:27:22 +02:00
script_props[name].append(p)
2017-08-08 19:56:47 +02:00
script_props_defaults[name].append(v)
2017-07-24 02:27:22 +02:00
readprop = l.strip().startswith('@prop')
2017-02-22 17:32:34 +01:00
def fetch_script_names():
if bpy.data.filepath == "":
return
wrd = bpy.data.worlds['Arm']
2017-05-26 16:05:14 +02:00
# Sources
2017-08-21 12:17:55 +02:00
wrd.arm_scripts_list.clear()
2017-05-13 17:17:43 +02:00
sources_path = get_fp() + '/Sources/' + safestr(wrd.arm_project_package)
2016-07-10 00:51:39 +02:00
if os.path.isdir(sources_path):
os.chdir(sources_path)
for file in glob.glob('*.hx'):
2017-07-24 02:27:22 +02:00
name = file.rsplit('.')[0]
2017-08-21 12:17:55 +02:00
wrd.arm_scripts_list.add().name = name
2017-07-24 02:27:22 +02:00
fetch_script_props(file)
2017-05-26 16:05:14 +02:00
# Canvas
2017-08-21 12:17:55 +02:00
wrd.arm_canvas_list.clear()
2017-05-26 16:05:14 +02:00
canvas_path = get_fp() + '/Bundled/canvas'
if os.path.isdir(canvas_path):
os.chdir(canvas_path)
for file in glob.glob('*.json'):
2017-08-21 12:17:55 +02:00
wrd.arm_canvas_list.add().name = file.rsplit('.')[0]
2016-07-10 00:51:39 +02:00
os.chdir(get_fp())
2016-07-17 23:29:30 +02:00
2017-07-24 02:27:22 +02:00
def fetch_trait_props():
for o in bpy.data.objects:
2017-08-21 12:17:55 +02:00
for item in o.arm_traitlist:
2017-07-24 02:27:22 +02:00
if item.name not in script_props:
continue
props = script_props[item.name]
2017-08-08 19:56:47 +02:00
defaults = script_props_defaults[item.name]
2017-07-24 02:27:22 +02:00
# Remove old props
2017-08-21 12:17:55 +02:00
for i in range(len(item.arm_traitpropslist) - 1, -1, -1):
ip = item.arm_traitpropslist[i]
2017-07-24 02:27:22 +02:00
if ip.name not in props:
2017-08-21 12:17:55 +02:00
item.arm_traitpropslist.remove(i)
2017-07-24 02:27:22 +02:00
# Add new props
2017-08-08 19:56:47 +02:00
for i in range(0, len(props)):
p = props[i]
2017-07-24 02:27:22 +02:00
found = False
2017-08-21 12:17:55 +02:00
for ip in item.arm_traitpropslist:
2017-07-24 02:27:22 +02:00
if ip.name == p:
found = True
break
if not found:
2017-08-21 12:17:55 +02:00
prop = item.arm_traitpropslist.add()
2017-07-24 02:27:22 +02:00
prop.name = p
2017-08-08 19:56:47 +02:00
prop.value = defaults[i]
2017-07-24 02:27:22 +02:00
2016-07-17 23:29:30 +02:00
def to_hex(val):
return '#%02x%02x%02x%02x' % (int(val[3] * 255), int(val[0] * 255), int(val[1] * 255), int(val[2] * 255))
2016-07-27 14:25:01 +02:00
def color_to_int(val):
return (int(val[3] * 255) << 24) + (int(val[0] * 255) << 16) + (int(val[1] * 255) << 8) + int(val[2] * 255)
2017-05-13 17:17:43 +02:00
def safesrc(s):
s = safestr(s).replace('.', '_').replace('-', '_').replace(' ', '')
2016-12-21 00:51:04 +01:00
if s[0].isdigit():
s = '_' + s
return s
2016-12-07 10:19:45 +01:00
2017-05-13 17:17:43 +02:00
def safestr(s):
for c in r'[]/\;,><&*:%=+@!#^()|?^':
s = s.replace(c, '_')
return ''.join([i if ord(i) < 128 else '_' for i in s])
2017-10-06 11:16:29 +02:00
def asset_name(bdata):
s = bdata.name
# Append library name if linked
if bdata.library != None:
s += '_' + bdata.library.name
return s
2017-05-13 17:17:43 +02:00
def asset_path(s):
2016-11-24 23:24:55 +01:00
return s[2:] if s[:2] == '//' else s # Remove leading '//'
2016-08-22 21:56:28 +02:00
2016-09-14 11:49:32 +02:00
def extract_filename(s):
2017-05-13 17:17:43 +02:00
return os.path.basename(asset_path(s))
2016-09-14 11:49:32 +02:00
2017-01-19 00:26:18 +01:00
def get_render_resolution(scene):
render = scene.render
2016-08-15 23:45:03 +02:00
scale = render.resolution_percentage / 100
return int(render.resolution_x * scale), int(render.resolution_y * scale)
2016-09-08 14:08:31 +02:00
def get_project_scene_name():
wrd = bpy.data.worlds['Arm']
2016-10-17 00:02:51 +02:00
if wrd.arm_play_active_scene:
2017-09-10 15:37:38 +02:00
if bpy.app.version >= (2, 80, 1): # 2.8
return bpy.context.scene.name
else:
return bpy.context.screen.scene.name
2016-09-08 14:08:31 +02:00
else:
2017-05-13 17:17:43 +02:00
return wrd.arm_project_scene
2016-09-12 02:24:20 +02:00
2017-01-19 00:26:18 +01:00
def get_active_scene():
wrd = bpy.data.worlds['Arm']
2017-09-10 15:37:38 +02:00
if bpy.app.version >= (2, 80, 1): # 2.8
context_scene = bpy.context.scene
else:
context_scene = bpy.context.screen.scene
2017-09-09 20:53:46 +02:00
return context_scene if wrd.arm_play_active_scene else bpy.data.scenes[wrd.arm_project_scene]
2017-01-19 00:26:18 +01:00
2017-07-03 15:16:15 +02:00
def logic_editor_space():
if hasattr(bpy.context, 'window') and bpy.context.window != None:
areas = bpy.context.window.screen.areas
for area in areas:
if area.type == 'NODE_EDITOR':
for space in area.spaces:
if space.type == 'NODE_EDITOR':
if space.node_tree != None and space.node_tree.bl_idname == 'ArmLogicTreeType': # and space.node_tree.is_updated:
return space
return None
2016-11-23 15:34:59 +01:00
krom_found = False
def with_krom():
global krom_found
return krom_found
2016-09-12 02:24:20 +02:00
2016-11-24 17:35:12 +01:00
glslver = 110
2016-11-22 15:02:03 +01:00
def glsl_version():
global glslver
return glslver
2016-12-08 14:38:04 +01:00
def check_saved(self):
if bpy.data.filepath == "":
self.report({"ERROR"}, "Save blend file first")
return False
return True
2017-09-08 14:07:41 +02:00
def check_path(s):
2017-01-12 22:20:14 +01:00
for c in r'[];><&*%=+@!#^()|?^':
2017-01-08 00:56:49 +01:00
if c in s:
return False
2017-01-23 20:41:45 +01:00
for c in s:
if ord(c) > 127:
return False
2017-01-08 00:56:49 +01:00
return True
2017-09-08 14:07:41 +02:00
def check_sdkpath(self):
s = get_sdk_path()
if check_path(s) == False:
self.report({"ERROR"}, "SDK path '{0}' contains special characters. Please move SDK to different path for now.".format(s))
return False
else:
return True
def check_projectpath(self):
s = get_fp()
if check_path(s) == False:
self.report({"WARNING"}, "Project path '{0}' contains special characters, build process may fail.".format(s))
return False
else:
return True
2017-07-27 10:23:59 +02:00
def check_engine(self):
if bpy.context == None or bpy.context.scene == None:
return
engine = bpy.context.scene.render.engine
2017-09-09 20:53:46 +02:00
if engine != 'CYCLES' and engine != 'BLENDER_EEVEE':
2017-07-27 10:23:59 +02:00
self.report({"ERROR"}, "Switch to Cycles or Eevee engine first")
return False
return True
2016-12-17 15:34:43 +01:00
def tess_enabled(target):
2017-08-21 20:16:06 +02:00
rpdat = get_rp()
return (target == 'krom' or target == 'native') and rpdat.arm_tessellation
2016-12-17 15:34:43 +01:00
2016-12-19 01:25:22 +01:00
def is_object_animation_enabled(bobject):
# Checks if animation is present and enabled
2017-08-21 12:17:55 +02:00
if bobject.arm_animation_enabled == False or bobject.type == 'BONE' or bobject.type == 'ARMATURE':
2016-12-19 01:25:22 +01:00
return False
if bobject.animation_data and bobject.animation_data.action:
return True
return False
def is_bone_animation_enabled(bobject):
# Checks if animation is present and enabled for parented armature
if bobject.parent and bobject.parent.type == 'ARMATURE':
2017-08-21 12:17:55 +02:00
if bobject.parent.arm_animation_enabled == False:
2016-12-19 01:25:22 +01:00
return False
if bobject.parent.animation_data and bobject.parent.animation_data.action:
return True
return False
2017-04-04 23:11:31 +02:00
def export_bone_data(bobject):
2017-10-10 09:57:23 +02:00
return bobject.find_armature() and is_bone_animation_enabled(bobject) and bpy.data.worlds['Arm'].arm_skin.startswith('GPU')
2017-04-04 23:11:31 +02:00
2016-09-12 02:24:20 +02:00
def register():
2016-11-23 15:34:59 +01:00
global krom_found
2016-11-22 15:02:03 +01:00
global glslver
2016-09-12 02:24:20 +02:00
import importlib.util
2016-10-19 13:28:06 +02:00
if importlib.util.find_spec('barmory') != None:
2016-11-23 15:34:59 +01:00
krom_found = True
2016-11-22 15:02:03 +01:00
import bgl
2016-11-26 12:17:33 +01:00
glslver = int(bgl.glGetString(bgl.GL_SHADING_LANGUAGE_VERSION).split(' ', 1)[0].replace('.', ''))
2016-09-12 02:24:20 +02:00
def unregister():
pass