2021-08-04 22:49:38 +02:00
|
|
|
from enum import Enum, unique
|
2020-01-07 11:44:33 +01:00
|
|
|
import glob
|
2016-06-30 13:22:05 +02:00
|
|
|
import json
|
2021-08-04 22:49:38 +02:00
|
|
|
import locale
|
2016-06-30 13:22:05 +02:00
|
|
|
import os
|
2016-08-12 02:29:09 +02:00
|
|
|
import platform
|
2020-06-14 23:04:49 +02:00
|
|
|
import re
|
2021-08-04 22:49:38 +02:00
|
|
|
import shlex
|
2017-11-20 14:32:36 +01:00
|
|
|
import subprocess
|
2021-03-13 21:12:12 +01:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2018-03-25 12:00:43 +02:00
|
|
|
import webbrowser
|
2020-01-07 11:44:33 +01:00
|
|
|
|
2018-12-24 15:46:41 +01:00
|
|
|
import numpy as np
|
2020-01-07 11:44:33 +01:00
|
|
|
|
|
|
|
import bpy
|
|
|
|
|
2017-05-17 17:06:52 +02:00
|
|
|
import arm.lib.armpack
|
2020-08-20 22:34:52 +02:00
|
|
|
from arm.lib.lz4 import LZ4
|
2018-05-24 22:16:28 +02:00
|
|
|
import arm.log as log
|
2020-01-07 11:44:33 +01:00
|
|
|
import arm.make_state as state
|
2020-10-10 20:34:39 +02:00
|
|
|
import arm.props_renderpath
|
2021-08-04 22:49:38 +02:00
|
|
|
|
2021-08-11 14:32:21 +02:00
|
|
|
if arm.is_reload(__name__):
|
2021-08-04 22:49:38 +02:00
|
|
|
arm.lib.armpack = arm.reload_module(arm.lib.armpack)
|
|
|
|
arm.lib.lz4 = arm.reload_module(arm.lib.lz4)
|
|
|
|
from arm.lib.lz4 import LZ4
|
|
|
|
log = arm.reload_module(log)
|
|
|
|
state = arm.reload_module(state)
|
|
|
|
arm.props_renderpath = arm.reload_module(arm.props_renderpath)
|
|
|
|
else:
|
2021-08-11 14:32:21 +02:00
|
|
|
arm.enable_reload(__name__)
|
2021-08-04 22:49:38 +02:00
|
|
|
|
2016-06-30 13:22:05 +02:00
|
|
|
|
2018-12-24 15:46:41 +01:00
|
|
|
class NumpyEncoder(json.JSONEncoder):
|
|
|
|
def default(self, obj):
|
|
|
|
if isinstance(obj, np.ndarray):
|
|
|
|
return obj.tolist()
|
|
|
|
return json.JSONEncoder.default(self, obj)
|
|
|
|
|
2020-09-25 21:20:11 +02:00
|
|
|
class WorkingDir:
|
|
|
|
"""Context manager for safely changing the current working directory."""
|
|
|
|
def __init__(self, cwd: str):
|
|
|
|
self.cwd = cwd
|
|
|
|
self.prev_cwd = os.getcwd()
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
os.chdir(self.cwd)
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
os.chdir(self.prev_cwd)
|
|
|
|
|
2016-07-20 17:33:17 +02:00
|
|
|
def write_arm(filepath, output):
|
2019-06-22 11:29:05 +02:00
|
|
|
if filepath.endswith('.lz4'):
|
2020-08-20 22:34:52 +02:00
|
|
|
with open(filepath, 'wb') as f:
|
|
|
|
packed = arm.lib.armpack.packb(output)
|
|
|
|
# Prepend packed data size for decoding. Haxe can't unpack
|
|
|
|
# an unsigned int64 so we use a signed int64 here
|
|
|
|
f.write(np.int64(LZ4.encode_bound(len(packed))).tobytes())
|
|
|
|
|
|
|
|
f.write(LZ4.encode(packed))
|
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:
|
2018-04-14 15:07:05 +02:00
|
|
|
filepath_json = filepath.split('.arm')[0] + '.json'
|
|
|
|
with open(filepath_json, 'w') as f:
|
2018-12-24 15:46:41 +01:00
|
|
|
f.write(json.dumps(output, sort_keys=True, indent=4, cls=NumpyEncoder))
|
2016-06-30 13:22:05 +02:00
|
|
|
|
2018-03-26 16:28:27 +02:00
|
|
|
def unpack_image(image, path, file_format='JPEG'):
|
|
|
|
print('Armory Info: Unpacking to ' + path)
|
2018-03-19 12:52:08 +01:00
|
|
|
image.filepath_raw = path
|
|
|
|
image.file_format = file_format
|
|
|
|
image.save()
|
2016-11-07 22:10:11 +01:00
|
|
|
|
2018-03-26 16:28:27 +02:00
|
|
|
def convert_image(image, path, file_format='JPEG'):
|
|
|
|
# Convert image to compatible format
|
|
|
|
print('Armory Info: Converting to ' + path)
|
|
|
|
ren = bpy.context.scene.render
|
|
|
|
orig_quality = ren.image_settings.quality
|
|
|
|
orig_file_format = ren.image_settings.file_format
|
2018-11-12 22:25:08 +01:00
|
|
|
orig_color_mode = ren.image_settings.color_mode
|
2019-12-21 20:23:30 +01:00
|
|
|
ren.image_settings.quality = get_texture_quality_percentage()
|
2018-03-26 16:28:27 +02:00
|
|
|
ren.image_settings.file_format = file_format
|
2018-11-12 22:25:08 +01:00
|
|
|
if file_format == 'PNG':
|
|
|
|
ren.image_settings.color_mode = 'RGBA'
|
2019-01-12 12:51:28 +01:00
|
|
|
image.save_render(path, scene=bpy.context.scene)
|
2018-03-26 16:28:27 +02:00
|
|
|
ren.image_settings.quality = orig_quality
|
|
|
|
ren.image_settings.file_format = orig_file_format
|
2018-11-12 22:25:08 +01:00
|
|
|
ren.image_settings.color_mode = orig_color_mode
|
2018-03-26 16:28:27 +02:00
|
|
|
|
2021-07-25 20:13:47 +02:00
|
|
|
|
|
|
|
def is_livepatch_enabled():
|
|
|
|
"""Returns whether live patch is enabled and can be used."""
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
# If the game is published, the target is krom-[OS] and not krom,
|
|
|
|
# so there is no live patch when publishing
|
|
|
|
return wrd.arm_live_patch and state.target == 'krom'
|
|
|
|
|
|
|
|
|
2017-05-23 01:03:44 +02:00
|
|
|
def blend_name():
|
2020-08-19 09:28:07 +02:00
|
|
|
return bpy.path.basename(bpy.context.blend_data.filepath).rsplit('.', 1)[0]
|
2017-05-23 01:03:44 +02:00
|
|
|
|
|
|
|
def build_dir():
|
|
|
|
return 'build_' + safestr(blend_name())
|
|
|
|
|
2021-10-25 19:43:13 +02:00
|
|
|
|
|
|
|
def get_fp() -> str:
|
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:
|
2021-09-04 02:46:25 +02:00
|
|
|
s = None
|
|
|
|
if use_local_sdk and bpy.data.filepath == '':
|
|
|
|
s = os.getcwd()
|
|
|
|
else:
|
|
|
|
s = bpy.data.filepath.split(os.path.sep)
|
|
|
|
s.pop()
|
|
|
|
s = os.path.sep.join(s)
|
2021-07-26 23:40:47 +02:00
|
|
|
if get_os_is_windows() and len(s) == 2 and s[1] == ':':
|
|
|
|
# If the project is located at a drive root (C:/ for example),
|
|
|
|
# then s = "C:". If joined later with another path, no path
|
|
|
|
# separator is added by default because C:some_path is valid
|
|
|
|
# Windows path syntax (some_path is then relative to the CWD on the
|
|
|
|
# C drive). We prevent this by manually adding the path separator
|
|
|
|
# in these cases. Please refer to the Python doc of os.path.join()
|
|
|
|
# for more details.
|
|
|
|
s += os.path.sep
|
|
|
|
return s
|
2016-06-30 13:22:05 +02:00
|
|
|
|
2021-10-25 19:43:13 +02:00
|
|
|
|
2017-05-23 01:03:44 +02:00
|
|
|
def get_fp_build():
|
2019-12-21 20:25:58 +01:00
|
|
|
return os.path.join(get_fp(), build_dir())
|
2017-05-23 01:03:44 +02:00
|
|
|
|
2021-10-25 19:43:13 +02:00
|
|
|
|
|
|
|
def to_absolute_path(path: str, from_library: Optional[bpy.types.Library] = None) -> str:
|
|
|
|
"""Convert the given absolute or relative path into an absolute path.
|
|
|
|
|
|
|
|
- If `from_library` is not set (default), a given relative path will be
|
|
|
|
interpreted as relative to the project directory.
|
|
|
|
- If `from_library` is set, a given relative path will be interpreted as
|
|
|
|
relative to the filepath of the specified library.
|
|
|
|
"""
|
|
|
|
return os.path.normpath(bpy.path.abspath(path, start=get_fp(), library=from_library))
|
|
|
|
|
|
|
|
|
2021-09-27 23:13:30 +02:00
|
|
|
def get_os() -> str:
|
2016-08-12 02:29:09 +02:00
|
|
|
s = platform.system()
|
|
|
|
if s == 'Windows':
|
|
|
|
return 'win'
|
|
|
|
elif s == 'Darwin':
|
|
|
|
return 'mac'
|
|
|
|
else:
|
|
|
|
return 'linux'
|
|
|
|
|
Added settings for building APK
Added a panel with settings:
- Building APK After Publish - to start the build after the project has been successfully published (False by default). Disabled if SDK path is not specified;
- Emulator - list of installed emulators in Android Studio (AVD Manager). At the start of Blender, the list is always empty, to fill and update it, you must click the Refresh button. To start the emulator, if you wish, you need to press the "Start" button (the list of emulators is obtained with the emulator -list-avds command, the launch is performed with the emulator -avd [name] command). The "Start" button is disabled if the name of the emulator is not selected from the list;
- Run Emulator After Building APK - launch the emulator after successfully building the APK file. Disabled if no APK build is installed or no emulator name selected.
To perform these operations, you need to specify the ANDROID_SDK_ROOT environment variable, if it is not specified in the OS, then the "Android SDK Path" setting is read and set as the environment variable os.environ ['ANDROID_SDK_ROOT'] to perform operations.
If no value is specified, then the user receives a corresponding message to the console. If the specified value is incorrect, then the user will receive messages from the corresponding programs.
2020-10-22 18:19:03 +02:00
|
|
|
def get_os_is_windows():
|
|
|
|
return True if get_os() == 'win' else False
|
|
|
|
|
Windows Settings – Publish and action after
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).
2020-11-24 18:41:50 +01:00
|
|
|
def get_os_is_windows_64():
|
|
|
|
if platform.machine().endswith('64'):
|
|
|
|
return True
|
|
|
|
# Checks if Python (32 bit) is running on Windows (64 bit)
|
|
|
|
if 'PROCESSOR_ARCHITEW6432' in os.environ:
|
|
|
|
return True
|
|
|
|
if os.environ['PROCESSOR_ARCHITECTURE'].endswith('64'):
|
|
|
|
return True
|
|
|
|
if 'PROGRAMFILES(X86)' in os.environ:
|
|
|
|
if os.environ['PROGRAMW6432'] is not None:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
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]
|
2017-11-20 14:32:36 +01:00
|
|
|
return getattr(item, target_to_gapi(item.arm_project_target))
|
2018-11-13 14:17:47 +01:00
|
|
|
if wrd.arm_runtime == 'Browser':
|
2018-03-24 14:46:54 +01:00
|
|
|
return 'webgl'
|
2019-07-15 09:21:23 +02:00
|
|
|
return 'direct3d11' if get_os() == 'win' else 'opengl'
|
2017-04-19 11:48:30 +02:00
|
|
|
|
2021-10-03 22:35:20 +02:00
|
|
|
|
2020-10-10 20:34:39 +02:00
|
|
|
def get_rp() -> arm.props_renderpath.ArmRPListItem:
|
2017-08-21 20:16:06 +02:00
|
|
|
wrd = bpy.data.worlds['Arm']
|
2021-10-03 22:35:20 +02:00
|
|
|
if not state.is_export and wrd.arm_play_renderpath != '':
|
|
|
|
return arm.props_renderpath.ArmRPListItem.get_by_name(wrd.arm_play_renderpath)
|
|
|
|
else:
|
|
|
|
return wrd.arm_rplist[wrd.arm_rplist_index]
|
|
|
|
|
2017-08-21 20:16:06 +02:00
|
|
|
|
2018-05-24 22:16:28 +02:00
|
|
|
def bundled_sdk_path():
|
|
|
|
if get_os() == 'mac':
|
|
|
|
# SDK on MacOS is located in .app folder due to security
|
|
|
|
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/'
|
|
|
|
elif get_os() == 'linux':
|
|
|
|
# /blender
|
|
|
|
return bpy.app.binary_path.rsplit('/', 1)[0] + '/armsdk/'
|
|
|
|
else:
|
|
|
|
# /blender.exe
|
|
|
|
return bpy.app.binary_path.replace('\\', '/').rsplit('/', 1)[0] + '/armsdk/'
|
|
|
|
|
2018-08-16 23:07:50 +02:00
|
|
|
# Passed by load_post handler when armsdk is found in project folder
|
|
|
|
use_local_sdk = False
|
2016-09-28 00:00:59 +02:00
|
|
|
def get_sdk_path():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2018-05-24 22:16:28 +02:00
|
|
|
p = bundled_sdk_path()
|
2018-08-16 23:07:50 +02:00
|
|
|
if use_local_sdk:
|
2021-01-24 16:53:34 +01:00
|
|
|
return os.path.normpath(get_fp() + '/armsdk/')
|
2018-08-16 23:07:50 +02:00
|
|
|
elif os.path.exists(p) and addon_prefs.sdk_bundled:
|
2021-01-24 16:53:34 +01:00
|
|
|
return os.path.normpath(p)
|
2016-09-28 00:00:59 +02:00
|
|
|
else:
|
2021-01-24 16:53:34 +01:00
|
|
|
return os.path.normpath(addon_prefs.sdk_path)
|
2016-09-28 00:00:59 +02:00
|
|
|
|
2020-09-22 20:53:57 +02:00
|
|
|
def get_last_commit():
|
|
|
|
p = get_sdk_path() + 'armory/.git/refs/heads/master'
|
|
|
|
|
|
|
|
try:
|
|
|
|
file = open(p, 'r')
|
|
|
|
commit = file.readline()
|
|
|
|
except:
|
|
|
|
commit = ''
|
|
|
|
return commit
|
|
|
|
|
2020-10-17 15:08:20 +02:00
|
|
|
|
|
|
|
def get_arm_preferences() -> bpy.types.AddonPreferences:
|
2019-05-05 22:39:05 +02:00
|
|
|
preferences = bpy.context.preferences
|
2020-10-17 15:08:20 +02:00
|
|
|
return preferences.addons["armory"].preferences
|
|
|
|
|
|
|
|
|
|
|
|
def get_ide_bin():
|
|
|
|
addon_prefs = get_arm_preferences()
|
2019-09-11 21:04:09 +02:00
|
|
|
return '' if not hasattr(addon_prefs, 'ide_bin') else addon_prefs.ide_bin
|
2019-05-05 22:39:05 +02:00
|
|
|
|
2016-09-28 00:00:59 +02:00
|
|
|
def get_ffmpeg_path():
|
2020-10-17 15:08:20 +02:00
|
|
|
return get_arm_preferences().ffmpeg_path
|
2016-09-28 00:00:59 +02:00
|
|
|
|
2018-03-22 21:43:22 +01:00
|
|
|
def get_renderdoc_path():
|
2020-10-17 15:08:20 +02:00
|
|
|
p = get_arm_preferences().renderdoc_path
|
2018-03-22 21:43:22 +01:00
|
|
|
if p == '' and get_os() == 'win':
|
|
|
|
pdefault = 'C:\\Program Files\\RenderDoc\\qrenderdoc.exe'
|
|
|
|
if os.path.exists(pdefault):
|
|
|
|
p = pdefault
|
|
|
|
return p
|
|
|
|
|
2018-03-25 12:00:43 +02:00
|
|
|
def get_code_editor():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2018-03-25 12:00:43 +02:00
|
|
|
return 'kodestudio' if not hasattr(addon_prefs, 'code_editor') else addon_prefs.code_editor
|
|
|
|
|
2018-03-25 21:48:30 +02:00
|
|
|
def get_ui_scale():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2018-03-25 21:48:30 +02:00
|
|
|
return 1.0 if not hasattr(addon_prefs, 'ui_scale') else addon_prefs.ui_scale
|
|
|
|
|
2018-08-30 15:42:25 +02:00
|
|
|
def get_khamake_threads():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2018-08-30 15:42:25 +02:00
|
|
|
return 1 if not hasattr(addon_prefs, 'khamake_threads') else addon_prefs.khamake_threads
|
|
|
|
|
2019-01-31 12:14:52 +01:00
|
|
|
def get_compilation_server():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2019-01-31 12:14:52 +01:00
|
|
|
return False if not hasattr(addon_prefs, 'compilation_server') else addon_prefs.compilation_server
|
|
|
|
|
2017-02-22 16:14:55 +01:00
|
|
|
def get_save_on_build():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2019-01-31 12:14:52 +01:00
|
|
|
return False if not hasattr(addon_prefs, 'save_on_build') else addon_prefs.save_on_build
|
2017-02-22 16:14:55 +01:00
|
|
|
|
2020-10-10 12:10:34 +02:00
|
|
|
def get_debug_console_auto():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2020-10-10 12:10:34 +02:00
|
|
|
return False if not hasattr(addon_prefs, 'debug_console_auto') else addon_prefs.debug_console_auto
|
|
|
|
|
|
|
|
def get_debug_console_visible_sc():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2020-10-10 12:10:34 +02:00
|
|
|
return 192 if not hasattr(addon_prefs, 'debug_console_visible_sc') else addon_prefs.debug_console_visible_sc
|
|
|
|
|
|
|
|
def get_debug_console_scale_in_sc():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2020-10-10 12:10:34 +02:00
|
|
|
return 219 if not hasattr(addon_prefs, 'debug_console_scale_in_sc') else addon_prefs.debug_console_scale_in_sc
|
|
|
|
|
|
|
|
def get_debug_console_scale_out_sc():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2020-10-10 12:10:34 +02:00
|
|
|
return 221 if not hasattr(addon_prefs, 'debug_console_scale_out_sc') else addon_prefs.debug_console_scale_out_sc
|
|
|
|
|
2017-11-10 14:53:40 +01:00
|
|
|
def get_viewport_controls():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2017-11-10 14:53:40 +01:00
|
|
|
return 'qwerty' if not hasattr(addon_prefs, 'viewport_controls') else addon_prefs.viewport_controls
|
|
|
|
|
2018-02-16 18:09:03 +01:00
|
|
|
def get_legacy_shaders():
|
2020-10-17 15:08:20 +02:00
|
|
|
addon_prefs = get_arm_preferences()
|
2018-02-16 18:09:03 +01:00
|
|
|
return False if not hasattr(addon_prefs, 'legacy_shaders') else addon_prefs.legacy_shaders
|
|
|
|
|
2018-08-16 20:48:00 +02:00
|
|
|
def get_relative_paths():
|
2020-10-17 15:08:20 +02:00
|
|
|
"""Whether to convert absolute paths to relative"""
|
|
|
|
addon_prefs = get_arm_preferences()
|
2018-08-16 20:48:00 +02:00
|
|
|
return False if not hasattr(addon_prefs, 'relative_paths') else addon_prefs.relative_paths
|
|
|
|
|
2020-10-31 01:09:33 +01:00
|
|
|
def get_pref_or_default(prop_name: str, default: Any) -> Any:
|
|
|
|
"""Return the preference setting for prop_name, or the value given as default if the property does not exist."""
|
|
|
|
addon_prefs = get_arm_preferences()
|
2020-10-31 13:18:05 +01:00
|
|
|
return getattr(addon_prefs, prop_name, default)
|
2020-10-31 01:09:33 +01:00
|
|
|
|
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'
|
2018-01-31 02:51:09 +01:00
|
|
|
return get_sdk_path() + '/Kha'
|
2017-03-20 13:23:31 +01:00
|
|
|
|
2017-07-02 20:48:19 +02:00
|
|
|
def get_haxe_path():
|
|
|
|
if get_os() == 'win':
|
2017-10-17 14:45:19 +02:00
|
|
|
return get_kha_path() + '/Tools/haxe/haxe.exe'
|
2017-07-02 20:48:19 +02:00
|
|
|
elif get_os() == 'mac':
|
2017-10-17 14:45:19 +02:00
|
|
|
return get_kha_path() + '/Tools/haxe/haxe-osx'
|
2017-07-02 20:48:19 +02:00
|
|
|
else:
|
2017-10-17 14:45:19 +02:00
|
|
|
return get_kha_path() + '/Tools/haxe/haxe-linux64'
|
2017-07-02 20:48:19 +02:00
|
|
|
|
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
|
|
|
|
2019-07-15 09:21:23 +02:00
|
|
|
def krom_paths():
|
2017-06-05 02:32:51 +02:00
|
|
|
sdk_path = get_sdk_path()
|
|
|
|
if arm.utils.get_os() == 'win':
|
2018-09-05 08:49:44 +02:00
|
|
|
krom_location = sdk_path + '/Krom'
|
2019-07-15 09:21:23 +02:00
|
|
|
krom_path = krom_location + '/Krom.exe'
|
2017-06-05 02:32:51 +02:00
|
|
|
elif arm.utils.get_os() == 'mac':
|
2018-09-05 08:49:44 +02:00
|
|
|
krom_location = sdk_path + '/Krom/Krom.app/Contents/MacOS'
|
2019-07-15 09:21:23 +02:00
|
|
|
krom_path = krom_location + '/Krom'
|
2017-06-05 02:32:51 +02:00
|
|
|
else:
|
2018-09-05 08:49:44 +02:00
|
|
|
krom_location = sdk_path + '/Krom'
|
2019-07-15 09:21:23 +02:00
|
|
|
krom_path = krom_location + '/Krom'
|
2017-06-05 02:32:51 +02:00
|
|
|
return krom_location, krom_path
|
|
|
|
|
2017-02-22 17:32:34 +01:00
|
|
|
def fetch_bundled_script_names():
|
2016-09-02 23:11:04 +02:00
|
|
|
wrd = bpy.data.worlds['Arm']
|
2017-08-21 12:17:55 +02:00
|
|
|
wrd.arm_bundled_scripts_list.clear()
|
2020-09-25 21:20:11 +02:00
|
|
|
|
|
|
|
with WorkingDir(get_sdk_path() + '/armory/Sources/armory/trait'):
|
|
|
|
for file in glob.glob('*.hx'):
|
|
|
|
wrd.arm_bundled_scripts_list.add().name = file.rsplit('.', 1)[0]
|
2017-02-22 17:32:34 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
|
2017-07-24 02:27:22 +02:00
|
|
|
script_props = {}
|
2017-08-08 19:56:47 +02:00
|
|
|
script_props_defaults = {}
|
2021-03-13 21:12:12 +01:00
|
|
|
script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message)
|
|
|
|
|
2021-03-14 15:50:27 +01:00
|
|
|
# See https://regex101.com/r/bbrCzN/8
|
2021-03-13 21:12:12 +01:00
|
|
|
RX_MODIFIERS = r'(?P<modifiers>(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers
|
|
|
|
RX_IDENTIFIER = r'(?P<identifier>[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules
|
2021-07-17 18:49:37 +02:00
|
|
|
RX_TYPE = r'(?:\s*:\s*(?P<type>[_a-z]+[\._a-z0-9]*))?' # Optional type annotation
|
2021-03-13 21:12:12 +01:00
|
|
|
RX_VALUE = r'(?:\s*=\s*(?P<value>(?:\".*\")|(?:[^;]+)|))?' # Optional default value
|
|
|
|
|
2021-03-14 15:50:27 +01:00
|
|
|
PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?P<attr_type>var|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};'
|
2021-03-13 21:12:12 +01:00
|
|
|
PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE)
|
|
|
|
def fetch_script_props(filename: str):
|
|
|
|
"""Parses @prop declarations from the given Haxe script."""
|
|
|
|
with open(filename, 'r', encoding='utf-8') as sourcefile:
|
|
|
|
source = sourcefile.read()
|
|
|
|
|
|
|
|
if source == '':
|
|
|
|
return
|
2019-12-21 14:21:19 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
name = filename.rsplit('.', 1)[0]
|
2017-10-25 14:39:41 +02:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Convert the name into a package path relative to the "Sources" dir
|
|
|
|
if 'Sources' in name:
|
|
|
|
name = name[name.index('Sources') + 8:]
|
|
|
|
if '/' in name:
|
|
|
|
name = name.replace('/', '.')
|
|
|
|
if '\\' in filename:
|
|
|
|
name = name.replace('\\', '.')
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
script_props[name] = []
|
|
|
|
script_props_defaults[name] = []
|
|
|
|
script_warnings[name] = []
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
for match in re.finditer(PROP_REGEX, source):
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
p_modifiers: Optional[str] = match.group('modifiers')
|
|
|
|
p_identifier: str = match.group('identifier')
|
|
|
|
p_type: Optional[str] = match.group('type')
|
|
|
|
p_default_val: Optional[str] = match.group('value')
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
if p_modifiers is not None:
|
|
|
|
if 'static' in p_modifiers:
|
2021-03-13 21:22:42 +01:00
|
|
|
script_warnings[name].append((p_identifier, '`static` modifier might cause unwanted behaviour!'))
|
2021-03-13 21:12:12 +01:00
|
|
|
if 'inline' in p_modifiers:
|
2021-03-13 21:22:42 +01:00
|
|
|
script_warnings[name].append((p_identifier, '`inline` modifier is not supported!'))
|
2021-03-13 21:12:12 +01:00
|
|
|
continue
|
2021-03-14 15:50:27 +01:00
|
|
|
if 'final' in p_modifiers or match.group('attr_type') == 'final':
|
|
|
|
script_warnings[name].append((p_identifier, '`final` properties are not supported!'))
|
2021-03-13 21:12:12 +01:00
|
|
|
continue
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Property type is annotated
|
|
|
|
if p_type is not None:
|
|
|
|
if p_type.startswith("iron.object."):
|
|
|
|
p_type = p_type[12:]
|
|
|
|
elif p_type.startswith("iron.math."):
|
|
|
|
p_type = p_type[10:]
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
type_default_val = get_type_default_value(p_type)
|
|
|
|
if type_default_val is None:
|
2021-03-13 21:22:42 +01:00
|
|
|
script_warnings[name].append((p_identifier, f'unsupported type `{p_type}`!'))
|
2021-03-13 21:12:12 +01:00
|
|
|
continue
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Default value exists
|
|
|
|
if p_default_val is not None:
|
|
|
|
# Remove string quotes
|
|
|
|
p_default_val = p_default_val.replace('\'', '').replace('"', '')
|
|
|
|
else:
|
|
|
|
p_default_val = type_default_val
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Default value is given instead, try to infer the properties type from it
|
|
|
|
elif p_default_val is not None:
|
|
|
|
p_type = get_prop_type_from_value(p_default_val)
|
2020-01-07 13:15:21 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Type is not recognized
|
|
|
|
if p_type is None:
|
|
|
|
script_warnings[name].append((p_identifier, 'could not infer property type from given value!'))
|
|
|
|
continue
|
|
|
|
if p_type == "String":
|
|
|
|
p_default_val = p_default_val.replace('\'', '').replace('"', '')
|
|
|
|
|
|
|
|
else:
|
|
|
|
script_warnings[name].append((p_identifier, 'missing type or default value!'))
|
|
|
|
continue
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2021-03-13 21:12:12 +01:00
|
|
|
# Register prop
|
|
|
|
prop = (p_identifier, p_type)
|
|
|
|
script_props[name].append(prop)
|
|
|
|
script_props_defaults[name].append(p_default_val)
|
2017-10-25 14:39:41 +02:00
|
|
|
|
2017-07-24 02:27:22 +02:00
|
|
|
|
2020-01-07 12:35:38 +01:00
|
|
|
def get_prop_type_from_value(value: str):
|
|
|
|
"""
|
|
|
|
Returns the property type based on its representation in the code.
|
|
|
|
|
|
|
|
If the type is not supported, `None` is returned.
|
|
|
|
"""
|
|
|
|
# Maybe ast.literal_eval() is better here?
|
|
|
|
try:
|
|
|
|
int(value)
|
|
|
|
return "Int"
|
|
|
|
except ValueError:
|
|
|
|
try:
|
|
|
|
float(value)
|
|
|
|
return "Float"
|
|
|
|
except ValueError:
|
2020-01-07 13:06:18 +01:00
|
|
|
# "" is required, " alone will not work
|
|
|
|
if len(value) > 1 and value.startswith(("\"", "'")) and value.endswith(("\"", "'")):
|
2020-01-07 12:35:38 +01:00
|
|
|
return "String"
|
|
|
|
if value in ("true", "false"):
|
|
|
|
return "Bool"
|
2020-01-07 20:26:12 +01:00
|
|
|
if value.startswith("new "):
|
2020-01-07 20:45:45 +01:00
|
|
|
value = value.split()[1].split("(")[0]
|
|
|
|
if value.startswith("Vec"):
|
|
|
|
return value
|
2020-01-08 16:37:33 +01:00
|
|
|
if value.startswith("iron.math.Vec"):
|
|
|
|
return value[10:]
|
2020-01-07 12:35:38 +01:00
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_type_default_value(prop_type: str):
|
|
|
|
"""
|
|
|
|
Returns the default value of the given Haxe type.
|
|
|
|
|
|
|
|
If the type is not supported, `None` is returned:
|
|
|
|
"""
|
|
|
|
if prop_type == "Int":
|
|
|
|
return 0
|
|
|
|
if prop_type == "Float":
|
|
|
|
return 0.0
|
2020-01-07 22:00:45 +01:00
|
|
|
if prop_type == "String" or prop_type in (
|
|
|
|
"Object", "CameraObject", "LightObject", "MeshObject", "SpeakerObject"):
|
2020-01-07 12:35:38 +01:00
|
|
|
return ""
|
|
|
|
if prop_type == "Bool":
|
|
|
|
return False
|
2020-01-07 20:26:12 +01:00
|
|
|
if prop_type == "Vec2":
|
|
|
|
return [0.0, 0.0]
|
|
|
|
if prop_type == "Vec3":
|
|
|
|
return [0.0, 0.0, 0.0]
|
|
|
|
if prop_type == "Vec4":
|
|
|
|
return [0.0, 0.0, 0.0, 0.0]
|
2020-01-07 12:35:38 +01:00
|
|
|
|
|
|
|
return None
|
|
|
|
|
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()
|
2020-07-12 15:36:18 +02:00
|
|
|
sources_path = os.path.join(get_fp(), 'Sources', safestr(wrd.arm_project_package))
|
2016-07-10 00:51:39 +02:00
|
|
|
if os.path.isdir(sources_path):
|
2020-09-25 21:20:11 +02:00
|
|
|
with WorkingDir(sources_path):
|
|
|
|
# Glob supports recursive search since python 3.5 so it should cover both blender 2.79 and 2.8 integrated python
|
|
|
|
for file in glob.glob('**/*.hx', recursive=True):
|
|
|
|
mod = file.rsplit('.', 1)[0]
|
|
|
|
mod = mod.replace('\\', '/')
|
|
|
|
mod_parts = mod.rsplit('/')
|
|
|
|
if re.match('^[A-Z][A-Za-z0-9_]*$', mod_parts[-1]):
|
|
|
|
wrd.arm_scripts_list.add().name = mod.replace('/', '.')
|
|
|
|
fetch_script_props(file)
|
2020-07-12 15:36:18 +02:00
|
|
|
|
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):
|
2020-09-25 21:20:11 +02:00
|
|
|
with WorkingDir(canvas_path):
|
|
|
|
for file in glob.glob('*.json'):
|
|
|
|
if file == "_themes.json":
|
|
|
|
continue
|
|
|
|
wrd.arm_canvas_list.add().name = file.rsplit('.', 1)[0]
|
2016-07-17 23:29:30 +02:00
|
|
|
|
2018-04-15 11:55:42 +02:00
|
|
|
def fetch_wasm_names():
|
|
|
|
if bpy.data.filepath == "":
|
|
|
|
return
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
# WASM modules
|
|
|
|
wrd.arm_wasm_list.clear()
|
|
|
|
sources_path = get_fp() + '/Bundled'
|
|
|
|
if os.path.isdir(sources_path):
|
2020-09-25 21:20:11 +02:00
|
|
|
with WorkingDir(sources_path):
|
|
|
|
for file in glob.glob('*.wasm'):
|
|
|
|
name = file.rsplit('.', 1)[0]
|
|
|
|
wrd.arm_wasm_list.add().name = name
|
2018-04-15 11:55:42 +02:00
|
|
|
|
2017-07-24 02:27:22 +02:00
|
|
|
def fetch_trait_props():
|
|
|
|
for o in bpy.data.objects:
|
2017-11-10 12:16:39 +01:00
|
|
|
fetch_prop(o)
|
2017-12-11 19:00:32 +01:00
|
|
|
for s in bpy.data.scenes:
|
|
|
|
fetch_prop(s)
|
2017-11-10 12:16:39 +01:00
|
|
|
|
|
|
|
def fetch_prop(o):
|
|
|
|
for item in o.arm_traitlist:
|
2019-08-11 16:44:49 +02:00
|
|
|
name = ''
|
|
|
|
if item.type_prop == 'Bundled Script':
|
|
|
|
name = 'armory.trait.' + item.name
|
|
|
|
else:
|
|
|
|
name = item.name
|
|
|
|
if name not in script_props:
|
2017-11-10 12:16:39 +01:00
|
|
|
continue
|
2019-08-11 16:44:49 +02:00
|
|
|
props = script_props[name]
|
|
|
|
defaults = script_props_defaults[name]
|
2020-01-07 13:15:21 +01:00
|
|
|
warnings = script_warnings[name]
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2017-11-10 12:16:39 +01:00
|
|
|
# Remove old props
|
|
|
|
for i in range(len(item.arm_traitpropslist) - 1, -1, -1):
|
|
|
|
ip = item.arm_traitpropslist[i]
|
2020-01-07 12:35:38 +01:00
|
|
|
if ip.name not in [p[0] for p in props]:
|
2017-11-10 12:16:39 +01:00
|
|
|
item.arm_traitpropslist.remove(i)
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2017-11-10 12:16:39 +01:00
|
|
|
# Add new props
|
2020-01-07 12:35:38 +01:00
|
|
|
for index, p in enumerate(props):
|
|
|
|
found_prop = False
|
|
|
|
for i_prop in item.arm_traitpropslist:
|
|
|
|
if i_prop.name == p[0]:
|
2020-01-07 13:12:35 +01:00
|
|
|
if i_prop.type == p[1]:
|
|
|
|
found_prop = i_prop
|
|
|
|
else:
|
|
|
|
item.arm_traitpropslist.remove(item.arm_traitpropslist.find(i_prop.name))
|
2017-11-10 12:16:39 +01:00
|
|
|
break
|
2020-01-07 12:35:38 +01:00
|
|
|
|
2017-11-10 12:16:39 +01:00
|
|
|
# Not in list
|
2020-01-07 12:35:38 +01:00
|
|
|
if not found_prop:
|
2017-11-10 12:16:39 +01:00
|
|
|
prop = item.arm_traitpropslist.add()
|
2020-01-07 12:35:38 +01:00
|
|
|
prop.name = p[0]
|
|
|
|
prop.type = p[1]
|
|
|
|
prop.set_value(defaults[index])
|
2017-11-10 12:16:39 +01:00
|
|
|
|
2020-01-07 12:35:38 +01:00
|
|
|
if found_prop:
|
|
|
|
prop = item.arm_traitpropslist[found_prop.name]
|
2017-11-10 12:16:39 +01:00
|
|
|
|
|
|
|
# Default value added and current value is blank (no override)
|
2020-02-07 19:29:55 +01:00
|
|
|
if (found_prop.get_value() is None
|
|
|
|
or found_prop.get_value() == "") and defaults[index]:
|
2020-01-07 12:35:38 +01:00
|
|
|
prop.set_value(defaults[index])
|
2017-11-10 12:16:39 +01:00
|
|
|
# Type has changed, update displayed name
|
2020-01-07 12:35:38 +01:00
|
|
|
if (len(found_prop.name) == 1 or (len(found_prop.name) > 1 and found_prop.name[1] != p[1])):
|
|
|
|
prop.name = p[0]
|
|
|
|
prop.type = p[1]
|
2017-10-25 14:39:41 +02:00
|
|
|
|
2020-01-07 13:15:21 +01:00
|
|
|
item.arm_traitpropswarnings.clear()
|
|
|
|
for warning in warnings:
|
|
|
|
entry = item.arm_traitpropswarnings.add()
|
2021-03-13 21:12:12 +01:00
|
|
|
entry.propName = warning[0]
|
|
|
|
entry.warning = warning[1]
|
2020-01-07 13:15:21 +01:00
|
|
|
|
2017-12-11 19:00:32 +01:00
|
|
|
def fetch_bundled_trait_props():
|
|
|
|
# Bundled script props
|
|
|
|
for o in bpy.data.objects:
|
|
|
|
for t in o.arm_traitlist:
|
|
|
|
if t.type_prop == 'Bundled Script':
|
|
|
|
file_path = get_sdk_path() + '/armory/Sources/armory/trait/' + t.name + '.hx'
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
fetch_script_props(file_path)
|
|
|
|
fetch_prop(o)
|
|
|
|
|
2019-02-05 12:59:34 +01:00
|
|
|
def update_trait_collections():
|
|
|
|
for col in bpy.data.collections:
|
|
|
|
if col.name.startswith('Trait|'):
|
|
|
|
bpy.data.collections.remove(col)
|
|
|
|
for o in bpy.data.objects:
|
|
|
|
for t in o.arm_traitlist:
|
|
|
|
if 'Trait|' + t.name not in bpy.data.collections:
|
|
|
|
col = bpy.data.collections.new('Trait|' + t.name)
|
|
|
|
else:
|
|
|
|
col = bpy.data.collections['Trait|' + t.name]
|
|
|
|
col.objects.link(o)
|
2017-12-11 00:55:26 +01: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
|
|
|
|
2016-09-02 23:11:04 +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
|
|
|
|
2020-04-16 00:34:03 +02:00
|
|
|
def safestr(s: str) -> str:
|
|
|
|
"""Outputs a string where special characters have been replaced with
|
|
|
|
'_', which can be safely used in file and path names."""
|
2021-03-27 00:30:05 +01:00
|
|
|
for c in r'''[]/\;,><&*:%=+@!#^()|?^'"''':
|
2017-05-13 17:17:43 +02:00
|
|
|
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):
|
2020-07-04 23:02:33 +02:00
|
|
|
if bdata == None:
|
|
|
|
return None
|
2017-10-06 11:16:29 +02:00
|
|
|
s = bdata.name
|
|
|
|
# Append library name if linked
|
2020-04-16 00:34:03 +02:00
|
|
|
if bdata.library is not None:
|
2017-10-06 11:16:29 +02:00
|
|
|
s += '_' + bdata.library.name
|
|
|
|
return s
|
|
|
|
|
2017-05-13 17:17:43 +02:00
|
|
|
def asset_path(s):
|
2019-12-21 20:25:58 +01:00
|
|
|
"""Remove leading '//'"""
|
|
|
|
return s[2:] if s[:2] == '//' else s
|
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
|
|
|
|
2019-12-21 20:23:30 +01:00
|
|
|
def get_texture_quality_percentage() -> int:
|
|
|
|
return int(bpy.data.worlds["Arm"].arm_texture_quality * 100)
|
|
|
|
|
2016-09-08 14:08:31 +02:00
|
|
|
def get_project_scene_name():
|
2017-11-09 15:05:58 +01:00
|
|
|
return get_active_scene().name
|
2016-09-12 02:24:20 +02:00
|
|
|
|
2021-10-03 22:35:20 +02:00
|
|
|
def get_active_scene() -> bpy.types.Scene:
|
2020-05-23 12:18:07 +02:00
|
|
|
wrd = bpy.data.worlds['Arm']
|
2017-11-09 15:05:58 +01:00
|
|
|
if not state.is_export:
|
2021-10-03 22:35:20 +02:00
|
|
|
if wrd.arm_play_scene is None:
|
2020-05-23 12:18:07 +02:00
|
|
|
return bpy.context.scene
|
|
|
|
return wrd.arm_play_scene
|
2017-09-10 15:37:38 +02:00
|
|
|
else:
|
2017-11-09 15:05:58 +01:00
|
|
|
item = wrd.arm_exporterlist[wrd.arm_exporterlist_index]
|
2019-01-24 15:09:49 +01:00
|
|
|
return item.arm_project_scene
|
2017-01-19 00:26:18 +01:00
|
|
|
|
2019-01-07 11:19:04 +01:00
|
|
|
def logic_editor_space(context_screen=None):
|
|
|
|
if context_screen == None:
|
|
|
|
context_screen = bpy.context.screen
|
|
|
|
if context_screen != None:
|
|
|
|
areas = context_screen.areas
|
2017-07-03 15:16:15 +02:00
|
|
|
for area in areas:
|
2019-02-16 00:37:14 +01:00
|
|
|
for space in area.spaces:
|
|
|
|
if space.type == 'NODE_EDITOR':
|
|
|
|
if space.node_tree != None and space.node_tree.bl_idname == 'ArmLogicTreeType':
|
|
|
|
return space
|
2017-07-03 15:16:15 +02:00
|
|
|
return None
|
|
|
|
|
2018-01-28 17:28:13 +01:00
|
|
|
def voxel_support():
|
2018-06-02 14:07:43 +02:00
|
|
|
# macos does not support opengl 4.5, needs metal
|
|
|
|
return state.target != 'html5' and get_os() != 'mac'
|
2018-01-28 17:28:13 +01:00
|
|
|
|
2019-01-21 17:59:25 +01:00
|
|
|
def get_cascade_size(rpdat):
|
|
|
|
cascade_size = int(rpdat.rp_shadowmap_cascade)
|
|
|
|
# Clamp to 4096 per cascade
|
|
|
|
if int(rpdat.rp_shadowmap_cascades) > 1 and cascade_size > 4096:
|
|
|
|
cascade_size = 4096
|
|
|
|
return cascade_size
|
|
|
|
|
2016-12-08 14:38:04 +01:00
|
|
|
def check_saved(self):
|
|
|
|
if bpy.data.filepath == "":
|
2018-05-24 22:16:28 +02:00
|
|
|
msg = "Save blend file first"
|
2020-07-07 22:43:51 +02:00
|
|
|
self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
|
2016-12-08 14:38:04 +01:00
|
|
|
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()
|
2020-07-07 22:43:51 +02:00
|
|
|
if not check_path(s):
|
|
|
|
msg = f"SDK path '{s}' contains special characters. Please move SDK to different path for now."
|
|
|
|
self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
|
2017-09-08 14:07:41 +02:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def check_projectpath(self):
|
|
|
|
s = get_fp()
|
2020-07-07 22:43:51 +02:00
|
|
|
if not check_path(s):
|
|
|
|
msg = f"Project path '{s}' contains special characters, build process may fail."
|
|
|
|
self.report({"ERROR"}, msg) if self is not None else log.warn(msg)
|
2017-09-08 14:07:41 +02:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2017-11-26 14:45:36 +01:00
|
|
|
def disp_enabled(target):
|
2017-08-21 20:16:06 +02:00
|
|
|
rpdat = get_rp()
|
2018-05-07 23:09:38 +02:00
|
|
|
if rpdat.arm_rp_displacement == 'Tessellation':
|
|
|
|
return target == 'krom' or target == 'native'
|
|
|
|
return rpdat.arm_rp_displacement != 'Off'
|
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
|
2018-05-18 13:40:01 +02:00
|
|
|
# Check for present actions
|
|
|
|
adata = bobject.parent.animation_data
|
|
|
|
has_actions = adata != None and adata.action != None
|
|
|
|
if not has_actions and adata != None:
|
|
|
|
if hasattr(adata, 'nla_tracks') and adata.nla_tracks != None:
|
|
|
|
for track in adata.nla_tracks:
|
|
|
|
if track.strips == None:
|
|
|
|
continue
|
|
|
|
for strip in track.strips:
|
|
|
|
if strip.action == None:
|
|
|
|
continue
|
|
|
|
has_actions = True
|
|
|
|
break
|
|
|
|
if has_actions:
|
|
|
|
break
|
|
|
|
if adata != None and has_actions:
|
2016-12-19 01:25:22 +01:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2020-04-12 21:16:16 +02:00
|
|
|
|
|
|
|
def export_bone_data(bobject: bpy.types.Object) -> bool:
|
|
|
|
"""Returns whether the bone data of the given object should be exported."""
|
2019-04-06 14:13:38 +02:00
|
|
|
return bobject.find_armature() and is_bone_animation_enabled(bobject) and get_rp().arm_skin == 'On'
|
2017-04-04 23:11:31 +02:00
|
|
|
|
2021-10-18 19:55:20 +02:00
|
|
|
def export_morph_targets(bobject: bpy.types.Object) -> bool:
|
2021-11-02 15:59:15 +01:00
|
|
|
if get_rp().arm_morph_target != 'On':
|
|
|
|
return False
|
2021-11-06 22:36:04 +01:00
|
|
|
|
2021-10-18 19:55:20 +02:00
|
|
|
if not hasattr(bobject.data, 'shape_keys'):
|
2021-11-02 15:59:15 +01:00
|
|
|
return False
|
2021-10-18 19:55:20 +02:00
|
|
|
|
|
|
|
shape_keys = bobject.data.shape_keys
|
2021-10-28 17:18:02 +02:00
|
|
|
if not shape_keys:
|
|
|
|
return False
|
|
|
|
if len(shape_keys.key_blocks) < 2:
|
|
|
|
return False
|
|
|
|
for shape_key in shape_keys.key_blocks[1:]:
|
|
|
|
if(not shape_key.mute):
|
|
|
|
return True
|
2021-10-27 16:25:11 +02:00
|
|
|
return False
|
2021-10-18 19:55:20 +02:00
|
|
|
|
|
|
|
def export_vcols(bobject: bpy.types.Object) -> bool:
|
|
|
|
for material in bobject.data.materials:
|
|
|
|
if material is not None and material.export_vcols:
|
|
|
|
return True
|
|
|
|
return False
|
2020-04-12 21:16:16 +02:00
|
|
|
|
2019-09-11 21:04:09 +02:00
|
|
|
def open_editor(hx_path=None):
|
|
|
|
ide_bin = get_ide_bin()
|
|
|
|
|
|
|
|
if hx_path is None:
|
|
|
|
hx_path = arm.utils.get_fp()
|
|
|
|
|
|
|
|
if get_code_editor() == 'default':
|
|
|
|
# Get editor environment variables
|
|
|
|
# https://unix.stackexchange.com/q/4859
|
|
|
|
env_v_editor = os.environ.get('VISUAL')
|
|
|
|
env_editor = os.environ.get('EDITOR')
|
|
|
|
|
|
|
|
if env_v_editor is not None:
|
|
|
|
ide_bin = env_v_editor
|
|
|
|
elif env_editor is not None:
|
|
|
|
ide_bin = env_editor
|
|
|
|
|
|
|
|
# No environment variables set -> Let the system decide how to
|
|
|
|
# open the file
|
2019-05-05 22:39:05 +02:00
|
|
|
else:
|
2019-09-11 21:04:09 +02:00
|
|
|
webbrowser.open('file://' + hx_path)
|
|
|
|
return
|
2019-05-05 22:39:05 +02:00
|
|
|
|
2019-09-11 21:04:09 +02:00
|
|
|
if os.path.exists(ide_bin):
|
|
|
|
args = [ide_bin, arm.utils.get_fp()]
|
2018-08-07 08:56:48 +02:00
|
|
|
|
2019-09-11 21:04:09 +02:00
|
|
|
# Sublime Text
|
2019-09-25 09:44:37 +02:00
|
|
|
if get_code_editor() == 'sublime':
|
2020-04-12 20:05:03 +02:00
|
|
|
project_name = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_name)
|
2019-09-11 21:04:09 +02:00
|
|
|
subl_project_path = arm.utils.get_fp() + f'/{project_name}.sublime-project'
|
|
|
|
|
|
|
|
if not os.path.exists(subl_project_path):
|
|
|
|
generate_sublime_project(subl_project_path)
|
|
|
|
|
|
|
|
args += ['--project', subl_project_path]
|
|
|
|
|
|
|
|
args.append('--add')
|
|
|
|
|
|
|
|
args.append(hx_path)
|
|
|
|
|
|
|
|
if arm.utils.get_os() == 'mac':
|
|
|
|
argstr = ""
|
|
|
|
|
|
|
|
for arg in args:
|
|
|
|
if not (arg.startswith('-') or arg.startswith('--')):
|
|
|
|
argstr += '"' + arg + '"'
|
|
|
|
argstr += ' '
|
|
|
|
|
|
|
|
subprocess.Popen(argstr[:-1], shell=True)
|
2018-03-25 12:00:43 +02:00
|
|
|
else:
|
2018-08-07 08:56:48 +02:00
|
|
|
subprocess.Popen(args)
|
2019-09-11 21:04:09 +02:00
|
|
|
|
2017-11-20 14:32:36 +01:00
|
|
|
else:
|
2019-09-11 21:04:09 +02:00
|
|
|
raise FileNotFoundError(f'Code editor executable not found: {ide_bin}. You can change the path in the Armory preferences.')
|
|
|
|
|
2020-10-17 15:08:56 +02:00
|
|
|
def open_folder(folder_path: str):
|
2019-09-26 16:46:18 +02:00
|
|
|
if arm.utils.get_os() == 'win':
|
2020-10-17 15:08:56 +02:00
|
|
|
subprocess.run(['explorer', folder_path])
|
2019-09-26 16:46:18 +02:00
|
|
|
elif arm.utils.get_os() == 'mac':
|
2020-10-17 15:08:56 +02:00
|
|
|
subprocess.run(['open', folder_path])
|
2019-09-26 16:46:18 +02:00
|
|
|
elif arm.utils.get_os() == 'linux':
|
2020-10-17 15:08:56 +02:00
|
|
|
subprocess.run(['xdg-open', folder_path])
|
2019-09-24 17:23:49 +02:00
|
|
|
else:
|
2020-10-17 15:08:56 +02:00
|
|
|
webbrowser.open('file://' + folder_path)
|
2019-09-24 17:23:49 +02:00
|
|
|
|
2019-09-11 21:04:09 +02:00
|
|
|
def generate_sublime_project(subl_project_path):
|
|
|
|
"""Generates a [project_name].sublime-project file."""
|
|
|
|
print('Generating Sublime Text project file')
|
|
|
|
|
|
|
|
project_data = {
|
2019-09-22 01:34:19 +02:00
|
|
|
"folders": [
|
|
|
|
{
|
|
|
|
"path": ".",
|
|
|
|
"file_exclude_patterns": ["*.blend*", "*.arm"]
|
|
|
|
},
|
2019-09-11 21:04:09 +02:00
|
|
|
],
|
|
|
|
}
|
|
|
|
|
|
|
|
with open(subl_project_path, 'w', encoding='utf-8') as project_file:
|
|
|
|
json.dump(project_data, project_file, ensure_ascii=False, indent=4)
|
2017-11-20 14:32:36 +01:00
|
|
|
|
|
|
|
def def_strings_to_array(strdefs):
|
|
|
|
defs = strdefs.split('_')
|
|
|
|
defs = defs[1:]
|
|
|
|
defs = ['_' + d for d in defs] # Restore _
|
|
|
|
return defs
|
|
|
|
|
|
|
|
def get_kha_target(target_name): # TODO: remove
|
2018-12-05 10:36:36 +01:00
|
|
|
if target_name == 'macos-hl':
|
|
|
|
return 'osx-hl'
|
|
|
|
elif target_name.startswith('krom'): # krom-windows
|
2018-03-04 19:38:40 +01:00
|
|
|
return 'krom'
|
2018-12-05 10:36:36 +01:00
|
|
|
elif target_name == 'custom':
|
|
|
|
return ''
|
2017-11-20 14:32:36 +01:00
|
|
|
return target_name
|
|
|
|
|
|
|
|
def target_to_gapi(arm_project_target):
|
|
|
|
# TODO: align target names
|
|
|
|
if arm_project_target == 'krom':
|
|
|
|
return 'arm_gapi_' + arm.utils.get_os()
|
2018-03-04 19:38:40 +01:00
|
|
|
elif arm_project_target == 'krom-windows':
|
|
|
|
return 'arm_gapi_win'
|
2018-04-06 17:39:22 +02:00
|
|
|
elif arm_project_target == 'windows-hl':
|
|
|
|
return 'arm_gapi_win'
|
2018-03-04 19:38:40 +01:00
|
|
|
elif arm_project_target == 'krom-linux':
|
|
|
|
return 'arm_gapi_linux'
|
2018-04-13 19:14:11 +02:00
|
|
|
elif arm_project_target == 'linux-hl':
|
|
|
|
return 'arm_gapi_linux'
|
2018-03-04 19:38:40 +01:00
|
|
|
elif arm_project_target == 'krom-macos':
|
|
|
|
return 'arm_gapi_mac'
|
2018-04-13 19:14:11 +02:00
|
|
|
elif arm_project_target == 'macos-hl':
|
|
|
|
return 'arm_gapi_mac'
|
2019-09-08 19:08:47 +02:00
|
|
|
elif arm_project_target == 'android-hl':
|
2017-11-20 14:32:36 +01:00
|
|
|
return 'arm_gapi_android'
|
2018-12-05 10:36:36 +01:00
|
|
|
elif arm_project_target == 'ios-hl':
|
|
|
|
return 'arm_gapi_ios'
|
2017-11-20 14:32:36 +01:00
|
|
|
elif arm_project_target == 'node':
|
|
|
|
return 'arm_gapi_html5'
|
2018-12-05 10:36:36 +01:00
|
|
|
else: # html5, custom
|
2017-11-20 14:32:36 +01:00
|
|
|
return 'arm_gapi_' + arm_project_target
|
|
|
|
|
2018-08-15 15:42:25 +02:00
|
|
|
def check_default_props():
|
2017-11-22 21:17:36 +01:00
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
if len(wrd.arm_rplist) == 0:
|
|
|
|
wrd.arm_rplist.add()
|
|
|
|
wrd.arm_rplist_index = 0
|
|
|
|
|
2018-08-15 15:42:25 +02:00
|
|
|
if wrd.arm_project_name == '':
|
|
|
|
# Take blend file name
|
|
|
|
wrd.arm_project_name = arm.utils.blend_name()
|
|
|
|
|
Add Android Settings + LN Set Vibrate
1. For the new settings to fully function, you need to update the submodules so that this Pull Request (https://github.com/Kode/kincmake/pull/100) gets into armsdk. Extended settings via khafile.js.
2. Added Android Settings panel:
- invisible until the target platform android-hl is added to the list;
- inactive until the target platform android-hl is selected in the list.
Options:
- Orientation;
- Compile Version SDK - from 26 to 30, default 29;
- Minimal Version SDK - from 14 to 30, default 14;
- Target Version SDK - from 26 to 30, default 29;
- Permissions - a list of permissions. If I will duplicate entries in the list, then only unique entries will be included during export. By default, the list is empty;
- Android ABI Filters - a list of platforms to build for (arm64-v8a, armeabi-v7a, x86, x86_64). If I will duplicate entries in the list, then only unique entries will be included during export. By default, the list is empty. If the list is empty, then all platforms will be used (as before).
3. The enum (names of permissions) and the function have been added to the utils.py modules, which adds the specified value to the list of permissions. Feature added for ease of use from different locations (different logical nodes).
4. List of permissions:
- ACCESS_COARSE_LOCATION - Allows an app to access approximate location;
- ACCESS_NETWORK_STATE - Allows applications to access information about networks;
- ACCESS_FINE_LOCATION - Allows an app to access precise location;
- ACCESS_WIFI_STATE - Allows applications to access information about Wi-Fi network;
- BLUETOOTH - Allows applications to connect to paired bluetooth devices;
- BLUETOOTH_ADMIN - Allows applications to discover and pair bluetooth devices;
- CAMERA - Required to be able to access the camera device;
- EXPAND_STATUS_BAR - Allows an application to expand or collapse the status bar;
- FOREGROUND_SERVICE - Allows a regular application to use Service.startForeground;
- GET_ACCOUNTS - Allows access to the list of accounts in the Accounts Service;
- INTERNET - Allows applications to open network sockets';
- READ_EXTERNAL_STORAGE - Allows an application to read from external storage;
- VIBRATE - Allows access to the vibrator;
- WRITE_EXTERNAL_STORAGE - Allows an application to write to external storage.
5. Added logical node Set Vibrate:
Category: Native
Pulses the vibration hardware on the device for time in milliseconds, if such hardware exists.
Input parameters:
- Milliseconds - time in milliseconds (data type Int, default value 100).
When adding the logical node Set Vibrate, the permission is automatically added to the list, even if the target android-hl has not been added to the export list (using a function from utils.py).
2020-10-17 15:47:54 +02:00
|
|
|
# Enum Permissions Name
|
|
|
|
class PermissionName(Enum):
|
|
|
|
ACCESS_COARSE_LOCATION = 'ACCESS_COARSE_LOCATION'
|
|
|
|
ACCESS_NETWORK_STATE = 'ACCESS_NETWORK_STATE'
|
|
|
|
ACCESS_FINE_LOCATION = 'ACCESS_FINE_LOCATION'
|
|
|
|
ACCESS_WIFI_STATE = 'ACCESS_WIFI_STATE'
|
|
|
|
BLUETOOTH = 'BLUETOOTH'
|
|
|
|
BLUETOOTH_ADMIN = 'BLUETOOTH_ADMIN'
|
|
|
|
CAMERA = 'CAMERA'
|
|
|
|
EXPAND_STATUS_BAR = 'EXPAND_STATUS_BAR'
|
|
|
|
FOREGROUND_SERVICE = 'FOREGROUND_SERVICE'
|
|
|
|
GET_ACCOUNTS = 'GET_ACCOUNTS'
|
|
|
|
INTERNET = 'INTERNET'
|
|
|
|
READ_EXTERNAL_STORAGE = 'READ_EXTERNAL_STORAGE'
|
|
|
|
VIBRATE = 'VIBRATE'
|
|
|
|
WRITE_EXTERNAL_STORAGE = 'WRITE_EXTERNAL_STORAGE'
|
|
|
|
|
|
|
|
# Add permission for target android
|
|
|
|
def add_permission_target_android(permission_name_enum):
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
check = False
|
|
|
|
for item in wrd.arm_exporter_android_permission_list:
|
|
|
|
if (item.arm_android_permissions.upper() == str(permission_name_enum.value).upper()):
|
|
|
|
check = True
|
|
|
|
break
|
|
|
|
if not check:
|
|
|
|
wrd.arm_exporter_android_permission_list.add()
|
|
|
|
wrd.arm_exporter_android_permission_list[len(wrd.arm_exporter_android_permission_list) - 1].arm_android_permissions = str(permission_name_enum.value).upper()
|
|
|
|
|
Added settings for building APK
Added a panel with settings:
- Building APK After Publish - to start the build after the project has been successfully published (False by default). Disabled if SDK path is not specified;
- Emulator - list of installed emulators in Android Studio (AVD Manager). At the start of Blender, the list is always empty, to fill and update it, you must click the Refresh button. To start the emulator, if you wish, you need to press the "Start" button (the list of emulators is obtained with the emulator -list-avds command, the launch is performed with the emulator -avd [name] command). The "Start" button is disabled if the name of the emulator is not selected from the list;
- Run Emulator After Building APK - launch the emulator after successfully building the APK file. Disabled if no APK build is installed or no emulator name selected.
To perform these operations, you need to specify the ANDROID_SDK_ROOT environment variable, if it is not specified in the OS, then the "Android SDK Path" setting is read and set as the environment variable os.environ ['ANDROID_SDK_ROOT'] to perform operations.
If no value is specified, then the user receives a corresponding message to the console. If the specified value is incorrect, then the user will receive messages from the corresponding programs.
2020-10-22 18:19:03 +02:00
|
|
|
def get_project_android_build_apk():
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
return wrd.arm_project_android_build_apk
|
|
|
|
|
|
|
|
def get_android_sdk_root_path():
|
|
|
|
if os.getenv('ANDROID_SDK_ROOT') == None:
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return '' if not hasattr(addon_prefs, 'android_sdk_root_path') else addon_prefs.android_sdk_root_path
|
|
|
|
else:
|
|
|
|
return os.getenv('ANDROID_SDK_ROOT')
|
|
|
|
|
2020-11-12 20:09:55 +01:00
|
|
|
def get_android_apk_copy_path():
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return '' if not hasattr(addon_prefs, 'android_apk_copy_path') else addon_prefs.android_apk_copy_path
|
|
|
|
|
|
|
|
def get_android_apk_copy_open_directory():
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return False if not hasattr(addon_prefs, 'android_apk_copy_open_directory') else addon_prefs.android_apk_copy_open_directory
|
|
|
|
|
Added settings for building APK
Added a panel with settings:
- Building APK After Publish - to start the build after the project has been successfully published (False by default). Disabled if SDK path is not specified;
- Emulator - list of installed emulators in Android Studio (AVD Manager). At the start of Blender, the list is always empty, to fill and update it, you must click the Refresh button. To start the emulator, if you wish, you need to press the "Start" button (the list of emulators is obtained with the emulator -list-avds command, the launch is performed with the emulator -avd [name] command). The "Start" button is disabled if the name of the emulator is not selected from the list;
- Run Emulator After Building APK - launch the emulator after successfully building the APK file. Disabled if no APK build is installed or no emulator name selected.
To perform these operations, you need to specify the ANDROID_SDK_ROOT environment variable, if it is not specified in the OS, then the "Android SDK Path" setting is read and set as the environment variable os.environ ['ANDROID_SDK_ROOT'] to perform operations.
If no value is specified, then the user receives a corresponding message to the console. If the specified value is incorrect, then the user will receive messages from the corresponding programs.
2020-10-22 18:19:03 +02:00
|
|
|
def get_android_emulators_list():
|
|
|
|
err = ''
|
|
|
|
items = []
|
|
|
|
path_file = get_android_emulator_file()
|
|
|
|
if len(path_file) > 0:
|
|
|
|
cmd = path_file + " -list-avds"
|
|
|
|
if get_os_is_windows():
|
|
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
|
|
else:
|
|
|
|
process = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
|
|
output = process.stdout.readline().decode("utf-8")
|
|
|
|
if len(output.strip()) == 0 and process.poll() is not None:
|
|
|
|
break
|
|
|
|
if output:
|
|
|
|
items.append(output.strip())
|
|
|
|
else:
|
|
|
|
err = 'File "'+ path_file +'" not found.'
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
def get_android_emulator_path():
|
|
|
|
return os.path.join(get_android_sdk_root_path(), "emulator")
|
|
|
|
|
|
|
|
def get_android_emulator_file():
|
|
|
|
path_file = ''
|
|
|
|
if get_os_is_windows():
|
|
|
|
path_file = os.path.join(get_android_emulator_path(), "emulator.exe")
|
|
|
|
else:
|
|
|
|
path_file = os.path.join(get_android_emulator_path(), "emulator")
|
|
|
|
# File Exists
|
|
|
|
return '' if not os.path.isfile(path_file) else path_file
|
|
|
|
|
|
|
|
def get_android_emulator_name():
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
return '' if not len(wrd.arm_project_android_list_avd.strip()) > 0 else wrd.arm_project_android_list_avd.strip()
|
|
|
|
|
|
|
|
def get_android_open_build_apk_directory():
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return False if not hasattr(addon_prefs, 'android_open_build_apk_directory') else addon_prefs.android_open_build_apk_directory
|
|
|
|
|
2020-11-13 19:45:31 +01:00
|
|
|
def get_html5_copy_path():
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return '' if not hasattr(addon_prefs, 'html5_copy_path') else addon_prefs.html5_copy_path
|
|
|
|
|
|
|
|
def get_link_web_server():
|
|
|
|
addon_prefs = get_arm_preferences()
|
|
|
|
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
|
|
|
|
|
2020-11-01 19:10:44 +01:00
|
|
|
def compare_version_blender_arm():
|
2021-05-01 00:04:25 +02:00
|
|
|
return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 93)
|
2020-11-01 19:10:44 +01:00
|
|
|
|
2021-07-10 21:46:44 +02:00
|
|
|
def get_file_arm_version_tuple() -> tuple[int]:
|
|
|
|
wrd = bpy.data.worlds['Arm']
|
|
|
|
return tuple(map(int, wrd.arm_version.split('.')))
|
|
|
|
|
2020-10-28 20:29:01 +01:00
|
|
|
def type_name_to_type(name: str) -> bpy.types.bpy_struct:
|
|
|
|
"""Return the Blender type given by its name, if registered."""
|
|
|
|
return bpy.types.bpy_struct.bl_rna_get_subclass_py(name)
|
|
|
|
|
Exporter Panel - Project Settings
1. Processing data entry into fields:
- Name - the field must not be empty. If the user tries to set an empty value, then this field specifies the name of the blend file (this is how it is implemented during assembly). The default is the name of the blend file.
- Package - the field must not be empty, it must not consist only of numbers and start with a number. The . symbol is replaced with _. The default is arm.
- Bundle - the field must be filled in according to the mask [string].[string]. Each part must not be empty, contain numbers, or start with a number. The default is org.armory3d. Previously, this field was blank, in which case the current default was used.
- Version.
Input in the Version field is processed and only allows input by masks:
- 0.0
- 0.0.0
- 0.0.0.0
In any case, the value is ignored and the previous value remains.
If the (Auto-increment Build Number) checkbox is checked and when performing the operation, Build and Publish will be automatically incremented by one after the version value.
Examples:
it was 1.0 - it is 1.1,
it was 1.9 - it is 1.10,
it was 1.0.1 - it is 1.0.2,
it was 0.1.1.2 - it is 0.1.1.3.
Defaults:
- Version - 1.0.0
- Auto-increment Build Number - enabled.
For all fields, special characters ([] / \ ;,> <& *:% = + @! # ^ () |? ^) Are also replaced with _.
2. Fixed display of Android Settings panel.
2020-11-13 18:36:27 +01:00
|
|
|
def change_version_project(version: str) -> str:
|
|
|
|
ver = version.strip().replace(' ', '').split('.')
|
|
|
|
v_i = int(ver[len(ver) - 1]) + 1
|
|
|
|
ver[len(ver) - 1] = str(v_i)
|
|
|
|
version = ''
|
|
|
|
for i in ver:
|
|
|
|
if len(version) > 0:
|
|
|
|
version += '.'
|
|
|
|
version += i
|
|
|
|
return version
|
|
|
|
|
Windows Settings – Publish and action after
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).
2020-11-24 18:41:50 +01:00
|
|
|
def get_visual_studio_from_version(version: str) -> str:
|
|
|
|
vs = [('10', '2010', 'Visual Studio 2010 (version 10)', 'vs2010'),
|
|
|
|
('11', '2012', 'Visual Studio 2012 (version 11)', 'vs2012'),
|
|
|
|
('12', '2013', 'Visual Studio 2013 (version 12)', 'vs2013'),
|
|
|
|
('14', '2015', 'Visual Studio 2015 (version 14)', 'vs2015'),
|
|
|
|
('15', '2017', 'Visual Studio 2017 (version 15)', 'vs2017'),
|
|
|
|
('16', '2019', 'Visual Studio 2019 (version 16)', 'vs2019')]
|
|
|
|
for item in vs:
|
|
|
|
if item[0] == version.strip():
|
|
|
|
return item[0], item[1], item[2], item[3]
|
|
|
|
|
|
|
|
def get_list_installed_vs(get_version: bool, get_name: bool, get_path: bool) -> []:
|
|
|
|
err = ''
|
|
|
|
items = []
|
2021-07-25 20:13:47 +02:00
|
|
|
path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe')
|
Windows Settings – Publish and action after
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).
2020-11-24 18:41:50 +01:00
|
|
|
if not os.path.isfile(path_file):
|
|
|
|
err = 'File "'+ path_file +'" not found.'
|
|
|
|
return items, err
|
2021-07-25 20:13:47 +02:00
|
|
|
|
Windows Settings – Publish and action after
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).
2020-11-24 18:41:50 +01:00
|
|
|
if (not get_version) and (not get_name) and (not get_path):
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
items_ver = []
|
|
|
|
items_name = []
|
|
|
|
items_path = []
|
|
|
|
count = 0
|
|
|
|
|
|
|
|
if get_version:
|
|
|
|
items_ver, err = get_list_installed_vs_version()
|
|
|
|
count_items = len(items_ver)
|
|
|
|
if len(err) > 0:
|
|
|
|
return items, err
|
|
|
|
if get_name:
|
|
|
|
items_name, err = get_list_installed_vs_name()
|
|
|
|
count_items = len(items_name)
|
|
|
|
if len(err) > 0:
|
|
|
|
return items, err
|
|
|
|
if get_path:
|
|
|
|
items_path, err = get_list_installed_vs_path()
|
|
|
|
count_items = len(items_path)
|
|
|
|
if len(err) > 0:
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
for i in range(count_items):
|
2021-07-25 20:13:47 +02:00
|
|
|
v = items_ver[i][0] if len(items_ver) > i else ''
|
|
|
|
v_full = items_ver[i][1] if len(items_ver) > i else ''
|
Windows Settings – Publish and action after
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).
2020-11-24 18:41:50 +01:00
|
|
|
n = items_name[i] if len(items_name) > i else ''
|
|
|
|
p = items_path[i] if len(items_path) > i else ''
|
|
|
|
items.append((v, n, p, v_full))
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
def get_list_installed_vs_version() -> []:
|
|
|
|
err = ''
|
|
|
|
items = []
|
|
|
|
path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe')
|
|
|
|
if os.path.isfile(path_file):
|
|
|
|
# Set arguments
|
|
|
|
cmd = path_file + ' -nologo -property installationVersion'
|
|
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
|
|
output = process.stdout.readline().decode("utf-8")
|
|
|
|
if len(output.strip()) == 0 and process.poll() is not None:
|
|
|
|
break
|
|
|
|
if output:
|
|
|
|
version_all = output.strip().replace(' ', '')
|
|
|
|
version_parse = version_all.split('.')
|
|
|
|
version_major = version_parse[0]
|
|
|
|
items.append((version_major, version_all))
|
|
|
|
else:
|
|
|
|
err = 'File "'+ path_file +'" not found.'
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
def get_list_installed_vs_name() -> []:
|
|
|
|
err = ''
|
|
|
|
items = []
|
|
|
|
path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe')
|
|
|
|
if os.path.isfile(path_file):
|
|
|
|
# Set arguments
|
|
|
|
cmd = path_file + ' -nologo -property displayName'
|
|
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
|
|
output = process.stdout.readline().decode(locale.getpreferredencoding())
|
|
|
|
if len(output.strip()) == 0 and process.poll() is not None:
|
|
|
|
break
|
|
|
|
if output:
|
|
|
|
name = safestr(output).replace('_', ' ').strip()
|
|
|
|
items.append(name)
|
|
|
|
else:
|
|
|
|
err = 'File "'+ path_file +'" not found.'
|
|
|
|
return items, err
|
|
|
|
|
|
|
|
def get_list_installed_vs_path() -> []:
|
|
|
|
err = ''
|
|
|
|
items = []
|
|
|
|
path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe')
|
|
|
|
if os.path.isfile(path_file):
|
|
|
|
# Set arguments
|
|
|
|
cmd = path_file + ' -nologo -property installationPath'
|
|
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
|
|
output = process.stdout.readline().decode("utf-8")
|
|
|
|
if len(output.strip()) == 0 and process.poll() is not None:
|
|
|
|
break
|
|
|
|
if output:
|
|
|
|
path = output.strip()
|
|
|
|
items.append(path)
|
|
|
|
else:
|
|
|
|
err = 'File "'+ path_file +'" not found.'
|
|
|
|
return items, err
|
|
|
|
|
2021-09-27 23:13:30 +02:00
|
|
|
|
|
|
|
def cpu_count(*, physical_only=False) -> Optional[int]:
|
|
|
|
"""Returns the number of logical (default) or physical CPUs.
|
|
|
|
The result can be `None` if `os.cpu_count()` was not able to get the
|
|
|
|
correct count of logical CPUs.
|
|
|
|
"""
|
|
|
|
if not physical_only:
|
|
|
|
return os.cpu_count()
|
|
|
|
|
2021-11-06 22:36:04 +01:00
|
|
|
err_reason = ''
|
|
|
|
command = []
|
|
|
|
|
2021-09-27 23:13:30 +02:00
|
|
|
_os = get_os()
|
|
|
|
try:
|
|
|
|
if _os == 'win':
|
2021-11-07 16:48:46 +01:00
|
|
|
sysroot = os.environ.get("SYSTEMROOT", default="C:\\WINDOWS")
|
|
|
|
command = [f'{sysroot}\\System32\\wbem\\wmic.exe', 'cpu', 'get', 'NumberOfCores']
|
2021-11-06 22:36:04 +01:00
|
|
|
result = subprocess.check_output(command)
|
2021-09-27 23:13:30 +02:00
|
|
|
result = result.decode('utf-8').splitlines()
|
|
|
|
result = int(result[2])
|
|
|
|
if result > 0:
|
|
|
|
return result
|
|
|
|
|
|
|
|
elif _os == 'linux':
|
2021-11-06 22:36:04 +01:00
|
|
|
command = ["grep -P '^core id' /proc/cpuinfo | sort -u | wc -l"]
|
|
|
|
result = subprocess.check_output(command[0], shell=True)
|
2021-09-27 23:13:30 +02:00
|
|
|
result = result.decode('utf-8').splitlines()
|
|
|
|
result = int(result[0])
|
|
|
|
if result > 0:
|
|
|
|
return result
|
|
|
|
|
|
|
|
# macOS
|
|
|
|
else:
|
2021-11-06 22:36:04 +01:00
|
|
|
command = ['sysctl', '-n', 'hw.physicalcpu']
|
|
|
|
return int(subprocess.check_output(command))
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
err_reason = f'Reason: command {command} exited with code {e.returncode}.'
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
err_reason = f'Reason: couldn\'t open file from command {command} ({e.errno=}).'
|
2021-09-27 23:13:30 +02:00
|
|
|
|
|
|
|
# Last resort even though it can be wrong
|
2021-11-06 22:36:04 +01:00
|
|
|
log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason)
|
2021-09-27 23:13:30 +02:00
|
|
|
return os.cpu_count()
|
|
|
|
|
|
|
|
|
2018-08-16 23:07:50 +02:00
|
|
|
def register(local_sdk=False):
|
|
|
|
global use_local_sdk
|
|
|
|
use_local_sdk = local_sdk
|
2016-09-12 02:24:20 +02:00
|
|
|
|
|
|
|
def unregister():
|
2021-03-13 21:12:12 +01:00
|
|
|
pass
|