forked from LeenkxTeam/LNXSDK
Merge pull request 'main' (#139) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#139
This commit is contained in:
87
leenkx.py
87
leenkx.py
@ -12,6 +12,7 @@ bl_info = {
|
|||||||
"tracker_url": "https://leenkx.com/support"
|
"tracker_url": "https://leenkx.com/support"
|
||||||
}
|
}
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import platform
|
import platform
|
||||||
@ -32,6 +33,41 @@ from bpy.app.handlers import persistent
|
|||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
from bpy.types import Operator, AddonPreferences
|
from bpy.types import Operator, AddonPreferences
|
||||||
|
|
||||||
|
LEENKX_PREFS_ENV = 'LEENKX_ADDON_PREFS'
|
||||||
|
|
||||||
|
persist_props = [
|
||||||
|
'apply_theme', 'show_advanced', 'sdk_path', 'tabs',
|
||||||
|
'code_editor', 'ide_bin', 'ui_scale', 'viewport_controls',
|
||||||
|
'khamake_threads', 'khamake_threads_use_auto', 'compilation_server',
|
||||||
|
'renderdoc_path', 'ffmpeg_path', 'save_on_build', 'open_build_directory',
|
||||||
|
'cmft_use_opencl', 'legacy_shaders', 'relative_paths',
|
||||||
|
'debug_console_auto', 'debug_console_visible_sc',
|
||||||
|
'debug_console_scale_in_sc', 'debug_console_scale_out_sc',
|
||||||
|
'android_sdk_root_path', 'android_open_build_apk_directory',
|
||||||
|
'android_apk_copy_path', 'android_apk_copy_open_directory',
|
||||||
|
'html5_copy_path', 'html5_server_port',
|
||||||
|
'html5_server_log', 'profile_exporter', 'khamake_debug',
|
||||||
|
'haxe_times', 'use_leenkx_py_symlink', 'update_submodules',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def save_prefs():
|
||||||
|
prefs = LeenkxAddonPreferences.get_prefs()
|
||||||
|
data = {k: getattr(prefs, k) for k in persist_props}
|
||||||
|
os.environ[LEENKX_PREFS_ENV] = json.dumps(data)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_prefs():
|
||||||
|
raw = os.environ.get(LEENKX_PREFS_ENV)
|
||||||
|
if not raw:
|
||||||
|
return
|
||||||
|
data = json.loads(raw)
|
||||||
|
prefs = LeenkxAddonPreferences.get_prefs()
|
||||||
|
prefs.skip_update = True
|
||||||
|
for k, v in data.items():
|
||||||
|
if hasattr(prefs, k):
|
||||||
|
setattr(prefs, k, v)
|
||||||
|
|
||||||
|
|
||||||
if bpy.app.version < (2, 90, 0):
|
if bpy.app.version < (2, 90, 0):
|
||||||
ListType = List
|
ListType = List
|
||||||
@ -116,7 +152,9 @@ class LeenkxAddonPreferences(AddonPreferences):
|
|||||||
return
|
return
|
||||||
self.skip_update = True
|
self.skip_update = True
|
||||||
self.sdk_path = bpy.path.reduce_dirs([bpy.path.abspath(self.sdk_path)])[0] + '/'
|
self.sdk_path = bpy.path.reduce_dirs([bpy.path.abspath(self.sdk_path)])[0] + '/'
|
||||||
|
save_prefs()
|
||||||
restart_leenkx(context)
|
restart_leenkx(context)
|
||||||
|
update_theme(context)
|
||||||
|
|
||||||
def ide_bin_update(self, context):
|
def ide_bin_update(self, context):
|
||||||
if self.skip_update:
|
if self.skip_update:
|
||||||
@ -311,6 +349,11 @@ class LeenkxAddonPreferences(AddonPreferences):
|
|||||||
" development. Warning: this will invalidate the installation if the SDK is removed"),
|
" development. Warning: this will invalidate the installation if the SDK is removed"),
|
||||||
update=lambda self, context: update_leenkx_py(get_sdk_path(context)),
|
update=lambda self, context: update_leenkx_py(get_sdk_path(context)),
|
||||||
)
|
)
|
||||||
|
apply_theme: BoolProperty(
|
||||||
|
name="Apply Leenkx Theme", default=True,
|
||||||
|
description="Automatically apply the Leenkx Blender theme on addon registration",
|
||||||
|
update=lambda self, context: update_theme(context),
|
||||||
|
)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
self.skip_update = False
|
self.skip_update = False
|
||||||
@ -450,6 +493,7 @@ class LeenkxAddonPreferences(AddonPreferences):
|
|||||||
|
|
||||||
col = box.column(align=True)
|
col = box.column(align=True)
|
||||||
col.prop(self, "use_leenkx_py_symlink")
|
col.prop(self, "use_leenkx_py_symlink")
|
||||||
|
col.prop(self, "apply_theme")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_prefs() -> 'LeenkxAddonPreferences':
|
def get_prefs() -> 'LeenkxAddonPreferences':
|
||||||
@ -458,9 +502,10 @@ class LeenkxAddonPreferences(AddonPreferences):
|
|||||||
|
|
||||||
|
|
||||||
def get_fp():
|
def get_fp():
|
||||||
if bpy.data.filepath == '':
|
filepath = getattr(bpy.data, 'filepath', '')
|
||||||
|
if filepath == '':
|
||||||
return ''
|
return ''
|
||||||
s = bpy.data.filepath.split(os.path.sep)
|
s = filepath.split(os.path.sep)
|
||||||
s.pop()
|
s.pop()
|
||||||
return os.path.sep.join(s)
|
return os.path.sep.join(s)
|
||||||
|
|
||||||
@ -934,13 +979,47 @@ def on_load_post(context):
|
|||||||
bpy.app.timers.register(lambda: restart_leenkx(bpy.context), first_interval=0.1)
|
bpy.app.timers.register(lambda: restart_leenkx(bpy.context), first_interval=0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_leenkx_theme():
|
||||||
|
if "leenkx.theme" in sys.modules:
|
||||||
|
sys.modules["leenkx.theme"].unregister()
|
||||||
|
del sys.modules["leenkx.theme"]
|
||||||
|
|
||||||
|
|
||||||
|
def apply_leenkx_theme(sdk_path: str):
|
||||||
|
remove_leenkx_theme()
|
||||||
|
import importlib.util
|
||||||
|
theme_path = os.path.join(sdk_path, "leenkx", "blender", "theme", "theme.py")
|
||||||
|
if os.path.exists(theme_path):
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"leenkx.theme", theme_path)
|
||||||
|
theme = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["leenkx.theme"] = theme
|
||||||
|
spec.loader.exec_module(theme)
|
||||||
|
theme.register(sdk_path)
|
||||||
|
else:
|
||||||
|
print("Leenkx theme: theme.py not found at", theme_path)
|
||||||
|
|
||||||
|
|
||||||
|
def update_theme(context):
|
||||||
|
prefs = LeenkxAddonPreferences.get_prefs()
|
||||||
|
if prefs.apply_theme:
|
||||||
|
sdk_path = get_sdk_path(context)
|
||||||
|
if sdk_path != "":
|
||||||
|
apply_leenkx_theme(sdk_path)
|
||||||
|
else:
|
||||||
|
remove_leenkx_theme()
|
||||||
|
|
||||||
|
|
||||||
def on_register_post():
|
def on_register_post():
|
||||||
detect_sdk_path()
|
detect_sdk_path()
|
||||||
|
save_prefs()
|
||||||
restart_leenkx(bpy.context)
|
restart_leenkx(bpy.context)
|
||||||
|
update_theme(bpy.context)
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(LeenkxAddonPreferences)
|
bpy.utils.register_class(LeenkxAddonPreferences)
|
||||||
|
restore_prefs()
|
||||||
bpy.utils.register_class(LnxAddonPrintVersionInfoButton)
|
bpy.utils.register_class(LnxAddonPrintVersionInfoButton)
|
||||||
bpy.utils.register_class(LnxAddonInstallButton)
|
bpy.utils.register_class(LnxAddonInstallButton)
|
||||||
bpy.utils.register_class(LnxAddonUpdateButton)
|
bpy.utils.register_class(LnxAddonUpdateButton)
|
||||||
@ -953,7 +1032,11 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
if bpy.app.timers.is_registered(on_register_post):
|
||||||
|
bpy.app.timers.unregister(on_register_post)
|
||||||
|
remove_leenkx_theme()
|
||||||
stop_leenkx()
|
stop_leenkx()
|
||||||
|
save_prefs()
|
||||||
bpy.utils.unregister_class(LeenkxAddonPreferences)
|
bpy.utils.unregister_class(LeenkxAddonPreferences)
|
||||||
bpy.utils.unregister_class(LnxAddonInstallButton)
|
bpy.utils.unregister_class(LnxAddonInstallButton)
|
||||||
bpy.utils.unregister_class(LnxAddonPrintVersionInfoButton)
|
bpy.utils.unregister_class(LnxAddonPrintVersionInfoButton)
|
||||||
|
|||||||
@ -69,7 +69,7 @@ void main() {
|
|||||||
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
n.xy = n.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);
|
||||||
n = normalize(n);
|
n = normalize(n);
|
||||||
|
|
||||||
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel);
|
float occ = 1.0 - traceShadow(P, n, voxels, voxelsSDF, normalize(lPos - P), clipmaps, pixel, vec2(0.0));
|
||||||
|
|
||||||
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
|
imageStore(voxels_shadows, ivec2(pixel), vec4(occ));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ def init_categories():
|
|||||||
lnx_nodes.add_category('Renderpath', icon='STICKY_UVS_LOC', section="graphics")
|
lnx_nodes.add_category('Renderpath', icon='STICKY_UVS_LOC', section="graphics")
|
||||||
|
|
||||||
lnx_nodes.add_category('Sound', icon='OUTLINER_OB_SPEAKER', section="sound")
|
lnx_nodes.add_category('Sound', icon='OUTLINER_OB_SPEAKER', section="sound")
|
||||||
lnx_nodes.add_category('3D_Audio', icon='SPEAKER', section="sound")
|
lnx_nodes.add_category('Audio_3D', icon='SPEAKER', section="sound")
|
||||||
|
|
||||||
lnx_nodes.add_category('Miscellaneous', icon='RESTRICT_COLOR_ON', section="misc")
|
lnx_nodes.add_category('Miscellaneous', icon='RESTRICT_COLOR_ON', section="misc")
|
||||||
lnx_nodes.add_category('Custom', icon='PLUGIN', section="misc")
|
lnx_nodes.add_category('Custom', icon='PLUGIN', section="misc")
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class AudioDSPFilterNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioDSPFilterNode'
|
bl_idname = 'LNAudioDSPFilterNode'
|
||||||
bl_label = 'DSP Filter'
|
bl_label = 'DSP Filter'
|
||||||
bl_description = 'DSP Audio Filter for lowpass/bandpass/highpass '
|
bl_description = 'DSP Audio Filter for lowpass/bandpass/highpass '
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
property0: HaxeEnumProperty(
|
||||||
@ -27,5 +27,5 @@ class AudioDSPFilterNode(LnxLogicTreeNode):
|
|||||||
layout.prop(self, 'property0')
|
layout.prop(self, 'property0')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioDSPFilterNode.on_register()
|
AudioDSPFilterNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioDSPHaasEffectNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioDSPHaasEffectNode'
|
bl_idname = 'LNAudioDSPHaasEffectNode'
|
||||||
bl_label = 'DSP Haas Effect'
|
bl_label = 'DSP Haas Effect'
|
||||||
bl_description = 'DSP Audio Haas Effect'
|
bl_description = 'DSP Audio Haas Effect'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
|
|
||||||
@ -19,5 +19,5 @@ class AudioDSPHaasEffectNode(LnxLogicTreeNode):
|
|||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioDSPHaasEffectNode.on_register()
|
AudioDSPHaasEffectNode.on_register()
|
||||||
@ -6,14 +6,14 @@ class AudioHRTFPannerNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioHRTFPannerNode'
|
bl_idname = 'LNAudioHRTFPannerNode'
|
||||||
bl_label = 'HRTF Panner'
|
bl_label = 'HRTF Panner'
|
||||||
bl_description = 'Create 3D audio HRTF Panner'
|
bl_description = 'Create 3D audio HRTF Panner'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
def update_input(self, context):
|
def update_input(self, context):
|
||||||
while len(self.inputs) > 5:
|
while len(self.inputs) > 5:
|
||||||
self.inputs.remove(self.inputs[-1])
|
self.inputs.remove(self.inputs[-1])
|
||||||
if self.property0 == 'Custom':
|
if self.property0 == 'Custom':
|
||||||
self.add_input('LnxStringSocket', 'File Name', default_value=0)
|
self.add_input('LnxStringSocket', 'File Name', default_value='')
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
property0: HaxeEnumProperty(
|
||||||
'property0',
|
'property0',
|
||||||
@ -35,5 +35,5 @@ class AudioHRTFPannerNode(LnxLogicTreeNode):
|
|||||||
layout.prop(self, 'property0')
|
layout.prop(self, 'property0')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioHRTFPannerNode.on_register()
|
AudioHRTFPannerNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioLoadNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioLoadNode'
|
bl_idname = 'LNAudioLoadNode'
|
||||||
bl_label = 'Load Audio'
|
bl_label = 'Load Audio'
|
||||||
bl_description = 'Load audio'
|
bl_description = 'Load audio'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
def update_input(self, context):
|
def update_input(self, context):
|
||||||
@ -39,5 +39,5 @@ class AudioLoadNode(LnxLogicTreeNode):
|
|||||||
#col.prop_search(self, 'property0', bpy.data, 'sounds', icon='NONE', text='')
|
#col.prop_search(self, 'property0', bpy.data, 'sounds', icon='NONE', text='')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioLoadNode.on_register()
|
AudioLoadNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioPauseNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioPauseNode'
|
bl_idname = 'LNAudioPauseNode'
|
||||||
bl_label = 'Pause Audio'
|
bl_label = 'Pause Audio'
|
||||||
bl_description = 'Trigger the pause function on audio'
|
bl_description = 'Trigger the pause function on audio'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
|
|
||||||
@ -18,5 +18,5 @@ class AudioPauseNode(LnxLogicTreeNode):
|
|||||||
self.add_output('LnxDynamicSocket', 'Audio')
|
self.add_output('LnxDynamicSocket', 'Audio')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioPauseNode.on_register()
|
AudioPauseNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioPlayNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioPlayNode'
|
bl_idname = 'LNAudioPlayNode'
|
||||||
bl_label = 'Play Audio'
|
bl_label = 'Play Audio'
|
||||||
bl_description = 'Trigger the play function on audio'
|
bl_description = 'Trigger the play function on audio'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
|
|
||||||
@ -19,5 +19,5 @@ class AudioPlayNode(LnxLogicTreeNode):
|
|||||||
self.add_output('LnxDynamicSocket', 'Audio')
|
self.add_output('LnxDynamicSocket', 'Audio')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioPlayNode.on_register()
|
AudioPlayNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioStereoPannerNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioStereoPannerNode'
|
bl_idname = 'LNAudioStereoPannerNode'
|
||||||
bl_label = 'Stereo Panner'
|
bl_label = 'Stereo Panner'
|
||||||
bl_description = 'Create 2D audio Stereo Panner'
|
bl_description = 'Create 2D audio Stereo Panner'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
def update_input(self, context):
|
def update_input(self, context):
|
||||||
@ -34,5 +34,5 @@ class AudioStereoPannerNode(LnxLogicTreeNode):
|
|||||||
layout.prop(self, 'property0')
|
layout.prop(self, 'property0')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioStereoPannerNode.on_register()
|
AudioStereoPannerNode.on_register()
|
||||||
@ -6,7 +6,7 @@ class AudioStopNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNAudioStopNode'
|
bl_idname = 'LNAudioStopNode'
|
||||||
bl_label = 'Stop Audio'
|
bl_label = 'Stop Audio'
|
||||||
bl_description = 'Trigger the stop function on audio'
|
bl_description = 'Trigger the stop function on audio'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
|
|
||||||
@ -19,5 +19,5 @@ class AudioStopNode(LnxLogicTreeNode):
|
|||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
AudioStopNode.on_register()
|
AudioStopNode.on_register()
|
||||||
@ -5,7 +5,7 @@ class GetAudioPositionNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNGetAudioPositionNode'
|
bl_idname = 'LNGetAudioPositionNode'
|
||||||
bl_label = 'Get Audio Position'
|
bl_label = 'Get Audio Position'
|
||||||
bl_description = 'Gets the current playback position of 3D audio in seconds.'
|
bl_description = 'Gets the current playback position of 3D audio in seconds.'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
@ -13,5 +13,5 @@ class GetAudioPositionNode(LnxLogicTreeNode):
|
|||||||
self.add_output('LnxFloatSocket', 'Position (seconds)')
|
self.add_output('LnxFloatSocket', 'Position (seconds)')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
GetAudioPositionNode.on_register()
|
GetAudioPositionNode.on_register()
|
||||||
@ -5,7 +5,7 @@ class SetAudioPositionNode(LnxLogicTreeNode):
|
|||||||
bl_idname = 'LNSetAudioPositionNode'
|
bl_idname = 'LNSetAudioPositionNode'
|
||||||
bl_label = 'Set Audio Position'
|
bl_label = 'Set Audio Position'
|
||||||
bl_description = 'Sets the playback position of 3D audio'
|
bl_description = 'Sets the playback position of 3D audio'
|
||||||
lnx_category = '3D_Audio'
|
lnx_category = 'Audio_3D'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|
||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
@ -16,5 +16,5 @@ class SetAudioPositionNode(LnxLogicTreeNode):
|
|||||||
self.add_output('LnxNodeSocketAction', 'Out')
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
add_category('3D_Audio', icon='SOUND')
|
add_category('Audio_3D', icon='SOUND')
|
||||||
SetAudioPositionNode.on_register()
|
SetAudioPositionNode.on_register()
|
||||||
@ -256,7 +256,10 @@ def register():
|
|||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for cls in reversed(__REG_CLASSES):
|
for cls in reversed(__REG_CLASSES):
|
||||||
bpy.utils.unregister_class(cls)
|
try:
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
del bpy.types.Object.lnx_propertylist
|
del bpy.types.Object.lnx_propertylist
|
||||||
del bpy.types.Object.lnx_propertylist_index
|
del bpy.types.Object.lnx_propertylist_index
|
||||||
del bpy.types.Scene.lnx_propertylist
|
del bpy.types.Scene.lnx_propertylist
|
||||||
|
|||||||
@ -292,39 +292,38 @@ class LNX_PT_TilesheetPanel(bpy.types.Panel):
|
|||||||
bl_label = "Leenkx Tilesheet"
|
bl_label = "Leenkx Tilesheet"
|
||||||
bl_space_type = "PROPERTIES"
|
bl_space_type = "PROPERTIES"
|
||||||
bl_region_type = "WINDOW"
|
bl_region_type = "WINDOW"
|
||||||
bl_context = "render"
|
bl_context = "object"
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.object is not None and context.object.type == 'MESH'
|
return context.object is not None and context.object.type == 'MESH'
|
||||||
|
|
||||||
def draw_header(self, context):
|
|
||||||
obj = context.object
|
|
||||||
self.layout.prop(obj, "lnx_tilesheet_enabled", text="")
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
obj = context.object
|
obj = context.object
|
||||||
|
|
||||||
layout.enabled = obj.lnx_tilesheet_enabled
|
layout.prop(obj, "lnx_tilesheet_enabled", text="Enable Tilesheet")
|
||||||
|
|
||||||
|
body = layout.column()
|
||||||
|
body.enabled = obj.lnx_tilesheet_enabled
|
||||||
|
|
||||||
# Start action dropdown
|
# Start action dropdown
|
||||||
layout.prop_search(obj, "lnx_tilesheet_default_action", obj, "lnx_tilesheet_actionlist", text="Start Action")
|
body.prop_search(obj, "lnx_tilesheet_default_action", obj, "lnx_tilesheet_actionlist", text="Start Action")
|
||||||
|
|
||||||
row = layout.row()
|
row = body.row()
|
||||||
row.prop(obj, "lnx_tilesheet_flipx")
|
row.prop(obj, "lnx_tilesheet_flipx")
|
||||||
row.prop(obj, "lnx_tilesheet_flipy")
|
row.prop(obj, "lnx_tilesheet_flipy")
|
||||||
|
|
||||||
# Actions list
|
# Actions list
|
||||||
layout.separator()
|
body.separator()
|
||||||
layout.label(text="Actions")
|
body.label(text="Actions")
|
||||||
rows = 2
|
rows = 2
|
||||||
if len(obj.lnx_tilesheet_actionlist) > 1:
|
if len(obj.lnx_tilesheet_actionlist) > 1:
|
||||||
rows = 4
|
rows = 4
|
||||||
row = layout.row()
|
row = body.row()
|
||||||
row.template_list("LNX_UL_TilesheetActionList", "The_List", obj, "lnx_tilesheet_actionlist", obj, "lnx_tilesheet_actionlist_index", rows=rows)
|
row.template_list("LNX_UL_TilesheetActionList", "The_List", obj, "lnx_tilesheet_actionlist", obj, "lnx_tilesheet_actionlist_index", rows=rows)
|
||||||
col = row.column(align=True)
|
col = row.column(align=True)
|
||||||
col.operator("lnx_tilesheetactionlist.new_item", icon='ADD', text="")
|
col.operator("lnx_tilesheetactionlist.new_item", icon='ADD', text="")
|
||||||
@ -340,7 +339,7 @@ class LNX_PT_TilesheetPanel(bpy.types.Panel):
|
|||||||
# Selected action details (per-action properties)
|
# Selected action details (per-action properties)
|
||||||
if obj.lnx_tilesheet_actionlist_index >= 0 and len(obj.lnx_tilesheet_actionlist) > 0:
|
if obj.lnx_tilesheet_actionlist_index >= 0 and len(obj.lnx_tilesheet_actionlist) > 0:
|
||||||
adat = obj.lnx_tilesheet_actionlist[obj.lnx_tilesheet_actionlist_index]
|
adat = obj.lnx_tilesheet_actionlist[obj.lnx_tilesheet_actionlist_index]
|
||||||
box = layout.box()
|
box = body.box()
|
||||||
# Grid dimensions
|
# Grid dimensions
|
||||||
row = box.row()
|
row = box.row()
|
||||||
row.use_property_split = False
|
row.use_property_split = False
|
||||||
|
|||||||
1271
leenkx/blender/theme/BlenderLeenkxTheme.xml
Normal file
1271
leenkx/blender/theme/BlenderLeenkxTheme.xml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
leenkx/blender/theme/LeenkxLogo.png
Normal file
BIN
leenkx/blender/theme/LeenkxLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
10
leenkx/blender/theme/blender_manifest.toml
Normal file
10
leenkx/blender/theme/blender_manifest.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
schema_version = "1.0.0"
|
||||||
|
id = "Leenkx_Theme"
|
||||||
|
name = "Leenkx Theme"
|
||||||
|
version = "1.0.0"
|
||||||
|
tagline = "Leenkx theme for Blender"
|
||||||
|
maintainer = "Onek8 <info@leenkx.com>"
|
||||||
|
type = "theme"
|
||||||
|
tags = ["Dark","Leenkx", "Solarized"]
|
||||||
|
blender_version_min = "5.0.0"
|
||||||
|
license = ["SPDX:GPL-2.0-or-later"]
|
||||||
BIN
leenkx/blender/theme/splash.png
Normal file
BIN
leenkx/blender/theme/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
240
leenkx/blender/theme/theme.py
Normal file
240
leenkx/blender/theme/theme.py
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Menu
|
||||||
|
|
||||||
|
preview_collection = None
|
||||||
|
logo_icon_id = 0
|
||||||
|
splash_icon_id = 0
|
||||||
|
original_splash_cls = None
|
||||||
|
|
||||||
|
|
||||||
|
def load_icons():
|
||||||
|
global preview_collection, logo_icon_id, splash_icon_id
|
||||||
|
if preview_collection is not None:
|
||||||
|
return
|
||||||
|
preview_collection = bpy.utils.previews.new()
|
||||||
|
theme_dir = os.path.dirname(__file__)
|
||||||
|
logo_path = os.path.join(theme_dir, "LeenkxLogo.png")
|
||||||
|
splash_path = os.path.join(theme_dir, "splash.png")
|
||||||
|
if os.path.exists(logo_path):
|
||||||
|
preview_collection.load("logo", logo_path, 'IMAGE', force_reload=True)
|
||||||
|
logo_icon_id = preview_collection["logo"].icon_id
|
||||||
|
else:
|
||||||
|
print("Leenkx theme: logo not found at", logo_path)
|
||||||
|
if os.path.exists(splash_path):
|
||||||
|
preview_collection.load("splash", splash_path, 'IMAGE', force_reload=True)
|
||||||
|
splash_icon_id = preview_collection["splash"].icon_id
|
||||||
|
else:
|
||||||
|
print("Leenkx theme: splash not found at", splash_path)
|
||||||
|
|
||||||
|
|
||||||
|
def install_theme(sdk_path: str):
|
||||||
|
theme_xml = os.path.join(sdk_path, "leenkx", "blender",
|
||||||
|
"theme", "BlenderLeenkxTheme.xml")
|
||||||
|
if not os.path.exists(theme_xml):
|
||||||
|
print("Leenkx theme: theme XML not found at", theme_xml)
|
||||||
|
return
|
||||||
|
|
||||||
|
bpy.ops.preferences.theme_install(
|
||||||
|
filepath=theme_xml, overwrite=True)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_splash(sdk_path: str):
|
||||||
|
splash_png = os.path.join(sdk_path, "leenkx", "blender",
|
||||||
|
"theme", "splash.png")
|
||||||
|
if os.path.exists(splash_png):
|
||||||
|
os.environ["BLENDER_CUSTOM_SPLASH"] = splash_png
|
||||||
|
else:
|
||||||
|
print("Leenkx splash: splash.png not found at", splash_png)
|
||||||
|
|
||||||
|
|
||||||
|
class LEENKX_MT_splash(Menu):
|
||||||
|
bl_label = "Splash"
|
||||||
|
bl_idname = "WM_MT_splash"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator_context = 'EXEC_DEFAULT'
|
||||||
|
layout.emboss = 'PULLDOWN_MENU'
|
||||||
|
|
||||||
|
split = layout.split()
|
||||||
|
|
||||||
|
col1 = split.column()
|
||||||
|
col1.label(text="New File")
|
||||||
|
|
||||||
|
bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
|
||||||
|
|
||||||
|
col2 = split.column()
|
||||||
|
col2_title = col2.row()
|
||||||
|
|
||||||
|
found_recent = col2.template_recent_files(rows=5)
|
||||||
|
|
||||||
|
if found_recent:
|
||||||
|
col2_title.label(text="Recent Files")
|
||||||
|
|
||||||
|
col_more = col2.column()
|
||||||
|
col_more.operator_context = 'INVOKE_DEFAULT'
|
||||||
|
more_props = col_more.operator("wm.search_single_menu", text="More...", icon='VIEWZOOM')
|
||||||
|
more_props.menu_idname = "TOPBAR_MT_file_open_recent"
|
||||||
|
else:
|
||||||
|
col2_title.label(text="Getting Started")
|
||||||
|
|
||||||
|
col2.operator("wm.url_open", text="Leenkx Docs",
|
||||||
|
icon='URL').url = "https://leenkx.com/docs"
|
||||||
|
col2.operator("wm.url_open", text="Support",
|
||||||
|
icon='URL').url = "https://leenkx.com/support"
|
||||||
|
col2.operator("wm.url_open", text="Community",
|
||||||
|
icon='URL').url = "https://leenkx.com/community"
|
||||||
|
col2.operator("wm.url_open", text="GitHub",
|
||||||
|
icon='URL').url = "https://github.com/leenkx/leenkx"
|
||||||
|
|
||||||
|
col_sep = layout.column()
|
||||||
|
col_sep.separator()
|
||||||
|
col_sep.separator(type='LINE')
|
||||||
|
col_sep.separator()
|
||||||
|
|
||||||
|
split = layout.split()
|
||||||
|
|
||||||
|
col1 = split.column()
|
||||||
|
sub = col1.row()
|
||||||
|
sub.operator_context = 'INVOKE_DEFAULT'
|
||||||
|
sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
|
||||||
|
col1.operator("wm.recover_last_session", icon='RECOVER_LAST')
|
||||||
|
|
||||||
|
col2 = split.column()
|
||||||
|
|
||||||
|
col2.operator("wm.url_open", text="Support Leenkx",
|
||||||
|
icon='FUND').url = "https://leenkx.com/donate"
|
||||||
|
col2.operator("wm.url_open", text="Leenkx Website",
|
||||||
|
icon='URL').url = "https://leenkx.com"
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
if (not bpy.app.online_access) and bpy.app.online_access_override:
|
||||||
|
self.layout.label(text="Running in Offline Mode", icon='INTERNET_OFFLINE')
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
|
||||||
|
def register_splash_menu():
|
||||||
|
global original_splash_cls
|
||||||
|
cls = getattr(bpy.types, "WM_MT_splash", None)
|
||||||
|
if cls is not None and cls is not LEENKX_MT_splash:
|
||||||
|
original_splash_cls = cls
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
bpy.utils.register_class(LEENKX_MT_splash)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister_splash_menu():
|
||||||
|
global original_splash_cls
|
||||||
|
bpy.utils.unregister_class(LEENKX_MT_splash)
|
||||||
|
if original_splash_cls is not None:
|
||||||
|
bpy.utils.register_class(original_splash_cls)
|
||||||
|
original_splash_cls = None
|
||||||
|
|
||||||
|
|
||||||
|
saved_draws = {}
|
||||||
|
|
||||||
|
|
||||||
|
def override_draw(bl_idname, new_draw):
|
||||||
|
cls = getattr(bpy.types, bl_idname, None)
|
||||||
|
if cls is None:
|
||||||
|
print("Leenkx menu: class not found", bl_idname)
|
||||||
|
return
|
||||||
|
if bl_idname not in saved_draws:
|
||||||
|
saved_draws[bl_idname] = cls.draw
|
||||||
|
elif cls.draw is new_draw:
|
||||||
|
return
|
||||||
|
cls.draw = new_draw
|
||||||
|
|
||||||
|
|
||||||
|
def restore_draw(bl_idname):
|
||||||
|
cls = getattr(bpy.types, bl_idname, None)
|
||||||
|
if cls is not None and bl_idname in saved_draws:
|
||||||
|
cls.draw = saved_draws[bl_idname]
|
||||||
|
|
||||||
|
def editor_menus_draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
if logo_icon_id:
|
||||||
|
layout.menu("TOPBAR_MT_blender", text="", icon_value=logo_icon_id)
|
||||||
|
else:
|
||||||
|
layout.menu("TOPBAR_MT_blender", text="Leenkx")
|
||||||
|
layout.menu("TOPBAR_MT_file")
|
||||||
|
layout.menu("TOPBAR_MT_edit")
|
||||||
|
layout.menu("TOPBAR_MT_render")
|
||||||
|
layout.menu("TOPBAR_MT_window")
|
||||||
|
layout.menu("TOPBAR_MT_help")
|
||||||
|
|
||||||
|
|
||||||
|
def blender_draw(self, _context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator("wm.splash", text="Leenkx Splash")
|
||||||
|
layout.separator()
|
||||||
|
layout.operator("preferences.app_template_install",
|
||||||
|
text="Install Application Template...")
|
||||||
|
layout.separator()
|
||||||
|
layout.menu("TOPBAR_MT_blender_system")
|
||||||
|
|
||||||
|
|
||||||
|
def help_draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
show_developer = context.preferences.view.show_developer_ui
|
||||||
|
|
||||||
|
layout.operator("wm.url_open", text="Leenkx Docs",
|
||||||
|
icon='URL').url = "https://leenkx.com/api"
|
||||||
|
layout.operator("wm.url_open", text="Leenkx Support",
|
||||||
|
icon='URL').url = "https://leenkx.com/contact"
|
||||||
|
layout.operator("wm.url_open", text="Tutorials",
|
||||||
|
icon='URL').url = "https://leenkx.com/tutorials"
|
||||||
|
layout.operator("wm.url_open", text="Community",
|
||||||
|
icon='URL').url = "https://leenkx.com/community"
|
||||||
|
layout.operator("wm.url_open", text="Support Leenkx (Donate)",
|
||||||
|
icon='FUND').url = "https://leenkx.com/donate"
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
if show_developer:
|
||||||
|
layout.operator("wm.url_open", text="Developer Docs",
|
||||||
|
icon='URL').url = "https://leenkx.com/wiki"
|
||||||
|
layout.operator("wm.url_open_preset",
|
||||||
|
text="Python API Reference").type = 'API'
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("wm.url_open", text="Report a Bug",
|
||||||
|
icon='URL').url = "https://leenkx.com/contact"
|
||||||
|
layout.operator("wm.sysinfo")
|
||||||
|
|
||||||
|
|
||||||
|
def register(sdk_path: str):
|
||||||
|
install_theme(sdk_path)
|
||||||
|
setup_splash(sdk_path)
|
||||||
|
load_icons()
|
||||||
|
register_splash_menu()
|
||||||
|
override_draw("TOPBAR_MT_editor_menus", editor_menus_draw)
|
||||||
|
override_draw("TOPBAR_MT_blender", blender_draw)
|
||||||
|
override_draw("TOPBAR_MT_help", help_draw)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
restore_draw("TOPBAR_MT_editor_menus")
|
||||||
|
restore_draw("TOPBAR_MT_blender")
|
||||||
|
restore_draw("TOPBAR_MT_help")
|
||||||
|
saved_draws.clear()
|
||||||
|
unregister_splash_menu()
|
||||||
|
|
||||||
|
global preview_collection
|
||||||
|
if preview_collection is not None:
|
||||||
|
bpy.utils.previews.remove(preview_collection)
|
||||||
|
preview_collection = None
|
||||||
|
os.environ.pop("BLENDER_CUSTOM_SPLASH", None)
|
||||||
|
|
||||||
|
window = bpy.context.window_manager.windows[0]
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
|
with bpy.context.temp_override(window=window):
|
||||||
|
bpy.ops.preferences.reset_default_theme()
|
||||||
|
else:
|
||||||
|
override = bpy.context.copy()
|
||||||
|
override['window'] = window
|
||||||
|
bpy.ops.preferences.reset_default_theme(override_context=override)
|
||||||
Reference in New Issue
Block a user