158 lines
3.9 KiB
Python
158 lines
3.9 KiB
Python
import bpy
|
|
from functools import partial
|
|
from typing import Protocol
|
|
from time import time
|
|
|
|
from ..third_party.boltons.funcutils import wraps
|
|
from ..utils.misc import show_message_box
|
|
from ..ui.locks import UIPropUpdateLock
|
|
|
|
|
|
def parametrized(dec):
|
|
def layer(*args, **kwargs):
|
|
def repl(f):
|
|
return dec(f, *args, **kwargs)
|
|
return repl
|
|
return layer
|
|
|
|
|
|
@parametrized
|
|
def delay_execution(func, delay_sec=1.0):
|
|
|
|
lock = False
|
|
def timer(*args, **kwargs):
|
|
nonlocal lock
|
|
lock = False
|
|
|
|
func(*args, **kwargs)
|
|
|
|
@wraps(func)
|
|
def wrapped(*args, **kwargs):
|
|
nonlocal lock
|
|
|
|
if not lock:
|
|
lock = True
|
|
bpy.app.timers.register(partial(timer, *args, **kwargs), first_interval=delay_sec)
|
|
|
|
return wrapped
|
|
|
|
|
|
@parametrized
|
|
def on_release(func, delay_sec=1.5):
|
|
|
|
exec_time = time()
|
|
|
|
def timer(*args, **kwargs):
|
|
nonlocal exec_time
|
|
if not abs(exec_time - time()) < delay_sec:
|
|
func(*args, **kwargs)
|
|
|
|
@wraps(func)
|
|
def wrapped(*args, **kwargs):
|
|
nonlocal exec_time
|
|
exec_time = time()
|
|
|
|
bpy.app.timers.register(partial(timer, *args, **kwargs), first_interval=max(1.0, delay_sec))
|
|
|
|
return wrapped
|
|
|
|
|
|
def no_recurse(func):
|
|
"""
|
|
Decorator to block the function's ability to indirectly call itself recursively.
|
|
:param func: Any func.
|
|
:return: Wrapped func.
|
|
"""
|
|
func.__no_recurse_lock = False
|
|
|
|
@wraps(func)
|
|
def wrapped(*args, **kwargs):
|
|
if func.__no_recurse_lock:
|
|
return
|
|
|
|
func.__no_recurse_lock = True
|
|
func(*args, **kwargs)
|
|
func.__no_recurse_lock = False
|
|
|
|
return wrapped
|
|
|
|
|
|
class StringFilterProtocol(Protocol):
|
|
"""
|
|
Protocol the filters for string_property_validator must implement.
|
|
"""
|
|
def __call__(self, string: str) -> None | str: ...
|
|
|
|
|
|
@no_recurse
|
|
def string_property_validator(self: bpy.types.PropertyGroup
|
|
, context: bpy.types.Context
|
|
, *
|
|
, name: str
|
|
, str_filter: StringFilterProtocol
|
|
, lockable: bool = True):
|
|
|
|
if lockable and UIPropUpdateLock().status:
|
|
return
|
|
|
|
cur_value = getattr(self, name)
|
|
|
|
filtered = str_filter(cur_value)
|
|
|
|
if filtered is None:
|
|
return
|
|
|
|
setattr(self, name, filtered)
|
|
|
|
|
|
def string_filter_internal_dir(string: str) -> None | str:
|
|
"""
|
|
Validate and attempt to fix the string that is supposed to represent internal WoW dir.
|
|
:param string: Any string.
|
|
:return: Fixed string or None if string was valid.
|
|
"""
|
|
|
|
if not string:
|
|
return None
|
|
|
|
slashes_fixed = False
|
|
if '/' in string:
|
|
string = string.replace('/', '\\')
|
|
slashes_fixed = True
|
|
|
|
if any(char == '.' for char in string):
|
|
show_message_box("Path must not contain '.' characters.", "Error", 'ERROR')
|
|
return ""
|
|
|
|
if any(char == ':' for char in string):
|
|
print("Path must not contain ':' characters. Path was probably added on import from a full disk filepath.")
|
|
return ""
|
|
|
|
# TODO: some characters like \ / : * ? " < > | are not valid path characters in windows
|
|
if any(char in ('*', '?', '"', '<', '>', '|') for char in string):
|
|
show_message_box("""Path must not contain * ? " < > | characters.""", "Error", 'ERROR')
|
|
return ""
|
|
|
|
try:
|
|
string.encode('ascii')
|
|
except UnicodeError:
|
|
show_message_box("Path must only use latin characters.", "Error", 'ERROR')
|
|
return ""
|
|
|
|
backslash_count = 0
|
|
for char in string:
|
|
if char == '\\':
|
|
backslash_count += 1
|
|
else:
|
|
backslash_count = 0
|
|
|
|
if backslash_count > 1:
|
|
show_message_box("Invalid path (multiple consecutive path separators)", "Error", 'ERROR')
|
|
return ""
|
|
|
|
if slashes_fixed:
|
|
return string
|
|
|
|
|
|
|