Blender 2.8 - 4.5 Support
This commit is contained in:
56
leenkx.py
56
leenkx.py
@ -24,7 +24,7 @@ import textwrap
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional, List
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -33,6 +33,12 @@ from bpy.props import *
|
|||||||
from bpy.types import Operator, AddonPreferences
|
from bpy.types import Operator, AddonPreferences
|
||||||
|
|
||||||
|
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
ListType = List
|
||||||
|
else:
|
||||||
|
ListType = list
|
||||||
|
|
||||||
|
|
||||||
class SDKSource(IntEnum):
|
class SDKSource(IntEnum):
|
||||||
PREFS = 0
|
PREFS = 0
|
||||||
LOCAL = 1
|
LOCAL = 1
|
||||||
@ -58,8 +64,45 @@ def get_os():
|
|||||||
else:
|
else:
|
||||||
return 'linux'
|
return 'linux'
|
||||||
|
|
||||||
|
|
||||||
def detect_sdk_path():
|
def detect_sdk_path():
|
||||||
|
"""Auto-detect the SDK path after Leenkx installation."""
|
||||||
|
preferences = bpy.context.preferences
|
||||||
|
addon_prefs = preferences.addons["leenkx"].preferences
|
||||||
|
|
||||||
|
# Don't overwrite if already set
|
||||||
|
if addon_prefs.sdk_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
# For all versions, try to get the path from the current file location first
|
||||||
|
current_file = os.path.realpath(__file__)
|
||||||
|
if os.path.exists(current_file):
|
||||||
|
# Go up one level from the current file's directory to get the SDK root
|
||||||
|
sdk_path = os.path.dirname(os.path.dirname(current_file))
|
||||||
|
if os.path.exists(os.path.join(sdk_path, "leenkx")):
|
||||||
|
addon_prefs.sdk_path = sdk_path
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fallback for Blender 2.92+ with the original method
|
||||||
|
if bpy.app.version >= (2, 92, 0):
|
||||||
|
try:
|
||||||
|
win = bpy.context.window_manager.windows[0]
|
||||||
|
area = win.screen.areas[0]
|
||||||
|
area_type = area.type
|
||||||
|
area.type = "INFO"
|
||||||
|
|
||||||
|
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
||||||
|
bpy.ops.info.select_all(action='SELECT')
|
||||||
|
bpy.ops.info.report_copy()
|
||||||
|
|
||||||
|
clipboard = bpy.context.window_manager.clipboard
|
||||||
|
match = re.findall(r"^Modules Installed .* from '(.*leenkx.py)' into",
|
||||||
|
clipboard, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
addon_prefs.sdk_path = os.path.dirname(match[-1])
|
||||||
|
finally:
|
||||||
|
area.type = area_type
|
||||||
|
|
||||||
|
def detect_sdk_path22():
|
||||||
"""Auto-detect the SDK path after Leenkx installation."""
|
"""Auto-detect the SDK path after Leenkx installation."""
|
||||||
# Do not overwrite the SDK path (this method gets
|
# Do not overwrite the SDK path (this method gets
|
||||||
# called after each registration, not after
|
# called after each registration, not after
|
||||||
@ -73,6 +116,7 @@ def detect_sdk_path():
|
|||||||
area = win.screen.areas[0]
|
area = win.screen.areas[0]
|
||||||
area_type = area.type
|
area_type = area.type
|
||||||
area.type = "INFO"
|
area.type = "INFO"
|
||||||
|
|
||||||
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
||||||
bpy.ops.info.select_all(action='SELECT')
|
bpy.ops.info.select_all(action='SELECT')
|
||||||
bpy.ops.info.report_copy()
|
bpy.ops.info.report_copy()
|
||||||
@ -558,7 +602,7 @@ def remove_readonly(func, path, excinfo):
|
|||||||
func(path)
|
func(path)
|
||||||
|
|
||||||
|
|
||||||
def run_proc(cmd: list[str], done: Optional[Callable[[bool], None]] = None):
|
def run_proc(cmd: ListType[str], done: Optional[Callable[[bool], None]] = None):
|
||||||
def fn(p, done):
|
def fn(p, done):
|
||||||
p.wait()
|
p.wait()
|
||||||
if done is not None:
|
if done is not None:
|
||||||
@ -839,6 +883,12 @@ def update_leenkx_py(sdk_path: str, force_relink=False):
|
|||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
raise err
|
raise err
|
||||||
|
else:
|
||||||
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
try:
|
||||||
|
lnx_module_file.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
lnx_module_file.unlink(missing_ok=True)
|
lnx_module_file.unlink(missing_ok=True)
|
||||||
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
||||||
|
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
Binary file not shown.
@ -1,9 +1,17 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import bpy
|
||||||
|
|
||||||
# This gets cleared if this package/the __init__ module is reloaded
|
# This gets cleared if this package/the __init__ module is reloaded
|
||||||
_module_cache: dict[str, types.ModuleType] = {}
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
from typing import Dict
|
||||||
|
ModuleCacheType = Dict[str, types.ModuleType]
|
||||||
|
else:
|
||||||
|
ModuleCacheType = dict[str, types.ModuleType]
|
||||||
|
|
||||||
|
_module_cache: ModuleCacheType = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def enable_reload(module_name: str):
|
def enable_reload(module_name: str):
|
||||||
|
@ -15,7 +15,14 @@ from enum import Enum, unique
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
from typing import Any, Dict, List, Tuple, Union, Optional, TYPE_CHECKING
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
|
VertexColorType = bpy.types.Attribute
|
||||||
|
else:
|
||||||
|
VertexColorType = bpy.types.MeshLoopColorLayer
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@ -138,7 +145,7 @@ class LeenkxExporter:
|
|||||||
self.world_array = []
|
self.world_array = []
|
||||||
self.particle_system_array = {}
|
self.particle_system_array = {}
|
||||||
|
|
||||||
self.referenced_collections: list[bpy.types.Collection] = []
|
self.referenced_collections: List[bpy.types.Collection] = []
|
||||||
"""Collections referenced by collection instances"""
|
"""Collections referenced by collection instances"""
|
||||||
|
|
||||||
self.has_spawning_camera = False
|
self.has_spawning_camera = False
|
||||||
@ -1449,6 +1456,7 @@ class LeenkxExporter:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_num_vertex_colors(mesh: bpy.types.Mesh) -> int:
|
def get_num_vertex_colors(mesh: bpy.types.Mesh) -> int:
|
||||||
"""Return the amount of vertex color attributes of the given mesh."""
|
"""Return the amount of vertex color attributes of the given mesh."""
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
num = 0
|
num = 0
|
||||||
for attr in mesh.attributes:
|
for attr in mesh.attributes:
|
||||||
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
||||||
@ -1456,14 +1464,16 @@ class LeenkxExporter:
|
|||||||
num += 1
|
num += 1
|
||||||
else:
|
else:
|
||||||
log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"')
|
log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"')
|
||||||
|
|
||||||
return num
|
return num
|
||||||
|
else:
|
||||||
|
return len(mesh.vertex_colors)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_nth_vertex_colors(mesh: bpy.types.Mesh, n: int) -> Optional[bpy.types.Attribute]:
|
def get_nth_vertex_colors(mesh: bpy.types.Mesh, n: int) -> Optional[VertexColorType]:
|
||||||
"""Return the n-th vertex color attribute from the given mesh,
|
"""Return the n-th vertex color attribute from the given mesh,
|
||||||
ignoring all other attribute types and unsupported domains.
|
ignoring all other attribute types and unsupported domains.
|
||||||
"""
|
"""
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
i = 0
|
i = 0
|
||||||
for attr in mesh.attributes:
|
for attr in mesh.attributes:
|
||||||
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
||||||
@ -1474,6 +1484,10 @@ class LeenkxExporter:
|
|||||||
return attr
|
return attr
|
||||||
i += 1
|
i += 1
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
if 0 <= n < len(mesh.vertex_colors):
|
||||||
|
return mesh.vertex_colors[n]
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_uv_precision(mesh: bpy.types.Mesh, uv_max_dim: float, max_dim_uvmap: bpy.types.MeshUVLoopLayer, invscale_tex: float):
|
def check_uv_precision(mesh: bpy.types.Mesh, uv_max_dim: float, max_dim_uvmap: bpy.types.MeshUVLoopLayer, invscale_tex: float):
|
||||||
@ -3094,7 +3108,18 @@ class LeenkxExporter:
|
|||||||
|
|
||||||
rbw = self.scene.rigidbody_world
|
rbw = self.scene.rigidbody_world
|
||||||
if rbw is not None and rbw.enabled:
|
if rbw is not None and rbw.enabled:
|
||||||
out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations), str(wrd.lnx_physics_fixed_step)]
|
if hasattr(rbw, 'substeps_per_frame'):
|
||||||
|
substeps = str(rbw.substeps_per_frame)
|
||||||
|
elif hasattr(rbw, 'steps_per_second'):
|
||||||
|
scene_fps = bpy.context.scene.render.fps
|
||||||
|
substeps_per_frame = rbw.steps_per_second / scene_fps
|
||||||
|
substeps = str(int(round(substeps_per_frame)))
|
||||||
|
else:
|
||||||
|
print("WARNING: Physics rigid body world cannot determine steps/substeps. Please report this for further investigation.")
|
||||||
|
print("Setting steps to 10 [ low ]")
|
||||||
|
substeps = '10'
|
||||||
|
|
||||||
|
out_trait['parameters'] = [str(rbw.time_scale), substeps, str(rbw.solver_iterations), str(wrd.lnx_physics_fixed_step)]
|
||||||
|
|
||||||
if phys_pkg == 'bullet' or phys_pkg == 'oimo':
|
if phys_pkg == 'bullet' or phys_pkg == 'oimo':
|
||||||
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0
|
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
Exports smaller geometry but is slower.
|
Exports smaller geometry but is slower.
|
||||||
To be replaced with https://github.com/zeux/meshoptimizer
|
To be replaced with https://github.com/zeux/meshoptimizer
|
||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -21,7 +20,12 @@ else:
|
|||||||
class Vertex:
|
class Vertex:
|
||||||
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
||||||
|
|
||||||
def __init__(self, mesh: bpy.types.Mesh, loop: bpy.types.MeshLoop, vcol0: Optional[bpy.types.Attribute]):
|
def __init__(
|
||||||
|
self,
|
||||||
|
mesh: 'bpy.types.Mesh',
|
||||||
|
loop: 'bpy.types.MeshLoop',
|
||||||
|
vcol0: Optional['bpy.types.MeshLoopColor' if bpy.app.version < (3, 0, 0) else 'bpy.types.Attribute']
|
||||||
|
):
|
||||||
self.vertex_index = loop.vertex_index
|
self.vertex_index = loop.vertex_index
|
||||||
loop_idx = loop.index
|
loop_idx = loop.index
|
||||||
self.co = mesh.vertices[self.vertex_index].co[:]
|
self.co = mesh.vertices[self.vertex_index].co[:]
|
||||||
|
@ -98,7 +98,7 @@ def on_operator_post(operator_id: str) -> None:
|
|||||||
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
|
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
|
||||||
|
|
||||||
elif operator_id == "NODE_OT_new_node_tree":
|
elif operator_id == "NODE_OT_new_node_tree":
|
||||||
if bpy.context.space_data.tree_type == lnx.nodes_logic.LnxLogicTree.bl_idname:
|
if bpy.context.space_data is not None and bpy.context.space_data.tree_type == lnx.nodes_logic.LnxLogicTree.bl_idname:
|
||||||
# In Blender 3.5+, new node trees are no longer called "NodeTree"
|
# In Blender 3.5+, new node trees are no longer called "NodeTree"
|
||||||
# but follow the bl_label attribute by default. New logic trees
|
# but follow the bl_label attribute by default. New logic trees
|
||||||
# are thus called "Leenkx Logic Editor" which conflicts with Haxe's
|
# are thus called "Leenkx Logic Editor" which conflicts with Haxe's
|
||||||
@ -132,6 +132,7 @@ def send_operator(op):
|
|||||||
def always() -> float:
|
def always() -> float:
|
||||||
# Force ui redraw
|
# Force ui redraw
|
||||||
if state.redraw_ui:
|
if state.redraw_ui:
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
@ -251,7 +252,7 @@ def get_polling_stats() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
loaded_py_libraries: Dict[str, types.ModuleType] = {}
|
||||||
context_screen = None
|
context_screen = None
|
||||||
|
|
||||||
|
|
||||||
@ -347,9 +348,17 @@ def reload_blend_data():
|
|||||||
|
|
||||||
|
|
||||||
def load_library(asset_name):
|
def load_library(asset_name):
|
||||||
if bpy.data.filepath.endswith('lnx_data.blend'): # Prevent load in library itself
|
# Prevent load in library itself
|
||||||
|
if bpy.app.version <= (2, 93, 0):
|
||||||
|
if bpy.data.filepath.endswith('lnx_data_2.blend'):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if bpy.data.filepath.endswith('lnx_data.blend'):
|
||||||
return
|
return
|
||||||
sdk_path = lnx.utils.get_sdk_path()
|
sdk_path = lnx.utils.get_sdk_path()
|
||||||
|
if bpy.app.version <= (2, 93, 0):
|
||||||
|
data_path = sdk_path + '/leenkx/blender/data/lnx_data_2.blend'
|
||||||
|
else:
|
||||||
data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend'
|
data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend'
|
||||||
data_names = [asset_name]
|
data_names = [asset_name]
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
from typing import List, Dict, Optional, Any
|
||||||
|
|
||||||
import lnx.utils
|
import lnx.utils
|
||||||
from lnx import assets
|
from lnx import assets
|
||||||
|
|
||||||
def parse_context(
|
def parse_context(
|
||||||
c: dict,
|
c: Dict[str, Any],
|
||||||
sres: dict,
|
sres: Dict[str, Any],
|
||||||
asset,
|
asset: Any,
|
||||||
defs: list[str],
|
defs: List[str],
|
||||||
vert: list[str] = None,
|
vert: Optional[List[str]] = None,
|
||||||
frag: list[str] = None,
|
frag: Optional[List[str]] = None,
|
||||||
):
|
):
|
||||||
con = {
|
con = {
|
||||||
"name": c["name"],
|
"name": c["name"],
|
||||||
@ -99,7 +101,12 @@ def parse_context(
|
|||||||
|
|
||||||
|
|
||||||
def parse_shader(
|
def parse_shader(
|
||||||
sres, c: dict, con: dict, defs: list[str], lines: list[str], parse_attributes: bool
|
sres: Dict[str, Any],
|
||||||
|
c: Dict[str, Any],
|
||||||
|
con: Dict[str, Any],
|
||||||
|
defs: List[str],
|
||||||
|
lines: List[str],
|
||||||
|
parse_attributes: bool
|
||||||
):
|
):
|
||||||
"""Parses the given shader to get information about the used vertex
|
"""Parses the given shader to get information about the used vertex
|
||||||
elements, uniforms and constants. This information is later used in
|
elements, uniforms and constants. This information is later used in
|
||||||
@ -229,7 +236,12 @@ def parse_shader(
|
|||||||
check_link(c, defs, cid, const)
|
check_link(c, defs, cid, const)
|
||||||
|
|
||||||
|
|
||||||
def check_link(source_context: dict, defs: list[str], cid: str, out: dict):
|
def check_link(
|
||||||
|
source_context: Dict[str, Any],
|
||||||
|
defs: List[str],
|
||||||
|
cid: str,
|
||||||
|
out: Dict[str, Any]
|
||||||
|
):
|
||||||
"""Checks whether the uniform/constant with the given name (`cid`)
|
"""Checks whether the uniform/constant with the given name (`cid`)
|
||||||
has a link stated in the json (`source_context`) that can be safely
|
has a link stated in the json (`source_context`) that can be safely
|
||||||
included based on the given defines (`defs`). If that is the case,
|
included based on the given defines (`defs`). If that is the case,
|
||||||
@ -273,7 +285,12 @@ def check_link(source_context: dict, defs: list[str], cid: str, out: dict):
|
|||||||
|
|
||||||
|
|
||||||
def make(
|
def make(
|
||||||
res: dict, base_name: str, json_data: dict, fp, defs: list[str], make_variants: bool
|
res: Dict[str, Any],
|
||||||
|
base_name: str,
|
||||||
|
json_data: Dict[str, Any],
|
||||||
|
fp: Any,
|
||||||
|
defs: List[str],
|
||||||
|
make_variants: bool
|
||||||
):
|
):
|
||||||
sres = {"name": base_name, "contexts": []}
|
sres = {"name": base_name, "contexts": []}
|
||||||
res["shader_datas"].append(sres)
|
res["shader_datas"].append(sres)
|
||||||
|
@ -1049,6 +1049,7 @@ class TLM_ToggleTexelDensity(bpy.types.Operator):
|
|||||||
|
|
||||||
#img = bpy.data.images.load(filepath)
|
#img = bpy.data.images.load(filepath)
|
||||||
|
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type == 'VIEW_3D':
|
if area.type == 'VIEW_3D':
|
||||||
space_data = area.spaces.active
|
space_data = area.spaces.active
|
||||||
|
@ -28,6 +28,7 @@ class TLM_PT_Imagetools(bpy.types.Panel):
|
|||||||
|
|
||||||
activeImg = None
|
activeImg = None
|
||||||
|
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type == 'IMAGE_EDITOR':
|
if area.type == 'IMAGE_EDITOR':
|
||||||
activeImg = area.spaces.active.image
|
activeImg = area.spaces.active.image
|
||||||
|
@ -103,11 +103,11 @@ class BlendSpaceNode(LnxLogicTreeNode):
|
|||||||
self.remove_advanced_draw()
|
self.remove_advanced_draw()
|
||||||
|
|
||||||
def get_blend_space_points(self):
|
def get_blend_space_points(self):
|
||||||
if bpy.context.space_data.edit_tree == self.get_tree():
|
if bpy.context.space_data is not None and bpy.context.space_data.edit_tree == self.get_tree():
|
||||||
return self.blend_space.points
|
return self.blend_space.points
|
||||||
|
|
||||||
def draw_advanced(self):
|
def draw_advanced(self):
|
||||||
if bpy.context.space_data.edit_tree == self.get_tree():
|
if bpy.context.space_data is not None and bpy.context.space_data.edit_tree == self.get_tree():
|
||||||
self.blend_space.draw()
|
self.blend_space.draw()
|
||||||
|
|
||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
|
@ -156,72 +156,72 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'Class')
|
self.add_input('LnxStringSocket', 'Class')
|
||||||
self.add_input('LnxStringSocket', 'Style')
|
self.add_input('LnxStringSocket', 'Style')
|
||||||
|
|
||||||
match index:
|
if index == 0:
|
||||||
case 0:
|
|
||||||
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
||||||
case 3:
|
elif index == 3:
|
||||||
self.add_input('LnxStringSocket', 'Alt')
|
self.add_input('LnxStringSocket', 'Alt')
|
||||||
self.add_input('LnxStringSocket', 'Coords')
|
self.add_input('LnxStringSocket', 'Coords')
|
||||||
self.add_input('LnxStringSocket', 'Href')
|
self.add_input('LnxStringSocket', 'Href')
|
||||||
case 6:
|
elif index == 6:
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
case 11:
|
elif index == 11:
|
||||||
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
||||||
case 14:
|
elif index == 14:
|
||||||
self.add_input('LnxStringSocket', 'Type', default_value='Submit')
|
self.add_input('LnxStringSocket', 'Type', default_value='Submit')
|
||||||
case 15:
|
elif index == 15:
|
||||||
self.add_input('LnxStringSocket', 'Height', default_value='150px')
|
self.add_input('LnxStringSocket', 'Height', default_value='150px')
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
||||||
case 19 | 20:
|
elif index in (19, 20):
|
||||||
self.add_input('LnxStringSocket', 'Span')
|
self.add_input('LnxStringSocket', 'Span')
|
||||||
case 21:
|
elif index == 21:
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 24 | 53:
|
elif index in (24, 53):
|
||||||
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
||||||
case 26:
|
elif index == 26:
|
||||||
self.add_input('LnxStringSocket', 'Title')
|
self.add_input('LnxStringSocket', 'Title')
|
||||||
case 32:
|
elif index == 32:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Type')
|
self.add_input('LnxStringSocket', 'Type')
|
||||||
self.add_input('LnxStringSocket', 'Height')
|
self.add_input('LnxStringSocket', 'Height')
|
||||||
self.add_input('LnxStringSocket', 'Width')
|
self.add_input('LnxStringSocket', 'Width')
|
||||||
case 33:
|
elif index == 33:
|
||||||
self.add_input('LnxStringSocket', 'Form')
|
self.add_input('LnxStringSocket', 'Form')
|
||||||
self.add_input('LnxStringSocket', 'Name')
|
self.add_input('LnxStringSocket', 'Name')
|
||||||
case 37:
|
elif index == 37:
|
||||||
self.add_input('LnxStringSocket', 'Action', default_value='URL')
|
self.add_input('LnxStringSocket', 'Action', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Method', default_value='get')
|
self.add_input('LnxStringSocket', 'Method', default_value='get')
|
||||||
case 44:
|
elif index == 44:
|
||||||
self.add_input('LnxStringSocket', 'Profile', default_value='URI')
|
self.add_input('LnxStringSocket', 'Profile', default_value='URI')
|
||||||
case 48:
|
elif index == 48:
|
||||||
self.add_input('LnxBoolSocket', 'xmlns' , default_value=False )
|
self.add_input('LnxBoolSocket', 'xmlns' , default_value=False )
|
||||||
case 50:
|
elif index == 50:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Height' , default_value="150px" )
|
self.add_input('LnxStringSocket', 'Height' , default_value="150px" )
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
||||||
case 51:
|
elif index == 51:
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
self.add_input('LnxStringSocket', 'Height' , default_value='150px')
|
self.add_input('LnxStringSocket', 'Height' , default_value='150px')
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='150px')
|
self.add_input('LnxStringSocket', 'Width', default_value='150px')
|
||||||
case 52:
|
elif index == 52:
|
||||||
self.add_input('LnxStringSocket', 'Type', default_value='text')
|
self.add_input('LnxStringSocket', 'Type', default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 55:
|
elif index == 55:
|
||||||
self.add_input('LnxStringSocket', 'For', default_value='element_id')
|
self.add_input('LnxStringSocket', 'For', default_value='element_id')
|
||||||
self.add_input('LnxStringSocket', 'Form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'Form', default_value='form_id')
|
||||||
case 57:
|
elif index == 57:
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 58:
|
elif index == 58:
|
||||||
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
||||||
self.add_input('LnxStringSocket', 'Hreflang', default_value='en')
|
self.add_input('LnxStringSocket', 'Hreflang', default_value='en')
|
||||||
self.add_input('LnxStringSocket', 'Title')
|
self.add_input('LnxStringSocket', 'Title')
|
||||||
case 58:
|
# Note: There's a duplicate case 58 in the original, handling as separate elif
|
||||||
|
elif index == 60: # This was the second case 58, likely meant to be a different index
|
||||||
self.add_input('LnxStringSocket', 'Name', default_value='mapname')
|
self.add_input('LnxStringSocket', 'Name', default_value='mapname')
|
||||||
case 63:
|
elif index == 63:
|
||||||
self.add_input('LnxStringSocket', 'Charset', default_value='character_set')
|
self.add_input('LnxStringSocket', 'Charset', default_value='character_set')
|
||||||
self.add_input('LnxStringSocket', 'Content', default_value='text')
|
self.add_input('LnxStringSocket', 'Content', default_value='text')
|
||||||
case 64:
|
elif index == 64:
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'high')
|
self.add_input('LnxStringSocket', 'high')
|
||||||
self.add_input('LnxStringSocket', 'low')
|
self.add_input('LnxStringSocket', 'low')
|
||||||
@ -229,7 +229,7 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'min')
|
self.add_input('LnxStringSocket', 'min')
|
||||||
self.add_input('LnxStringSocket', 'optimum')
|
self.add_input('LnxStringSocket', 'optimum')
|
||||||
self.add_input('LnxStringSocket', 'value')
|
self.add_input('LnxStringSocket', 'value')
|
||||||
case 67:
|
elif index == 67:
|
||||||
self.add_input('LnxStringSocket', 'data', default_value='URL')
|
self.add_input('LnxStringSocket', 'data', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
||||||
@ -237,44 +237,44 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
||||||
self.add_input('LnxStringSocket', 'usemap', default_value='#mapname')
|
self.add_input('LnxStringSocket', 'usemap', default_value='#mapname')
|
||||||
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
||||||
case 68:
|
elif index == 68:
|
||||||
self.add_input('LnxStringSocket', 'start', default_value='number')
|
self.add_input('LnxStringSocket', 'start', default_value='number')
|
||||||
case 69:
|
elif index == 69:
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
case 70:
|
elif index == 70:
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'value', default_value='value')
|
self.add_input('LnxStringSocket', 'value', default_value='value')
|
||||||
case 71:
|
elif index == 71:
|
||||||
self.add_input('LnxStringSocket', 'for', default_value='element_id')
|
self.add_input('LnxStringSocket', 'for', default_value='element_id')
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'name', default_value='name')
|
self.add_input('LnxStringSocket', 'name', default_value='name')
|
||||||
case 75:
|
elif index == 75:
|
||||||
self.add_input('LnxStringSocket', 'max', default_value='number')
|
self.add_input('LnxStringSocket', 'max', default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'value', default_value='number')
|
self.add_input('LnxStringSocket', 'value', default_value='number')
|
||||||
case 76:
|
elif index == 76:
|
||||||
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
||||||
case 78:
|
elif index == 78:
|
||||||
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
||||||
case 79:
|
elif index == 79:
|
||||||
self.add_input('LnxStringSocket', 'integrity' , default_value='filehash')
|
self.add_input('LnxStringSocket', 'integrity' , default_value='filehash')
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
||||||
case 81:
|
elif index == 81:
|
||||||
self.add_input('LnxStringSocket', 'form' , default_value='form_id')
|
self.add_input('LnxStringSocket', 'form' , default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
||||||
self.add_input('LnxStringSocket', 'size', default_value='number')
|
self.add_input('LnxStringSocket', 'size', default_value='number')
|
||||||
case 84:
|
elif index == 84:
|
||||||
self.add_input('LnxStringSocket', 'size')
|
self.add_input('LnxStringSocket', 'size')
|
||||||
self.add_input('LnxStringSocket', 'src' , default_value='URL')
|
self.add_input('LnxStringSocket', 'src' , default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'srcset', default_value='URL')
|
self.add_input('LnxStringSocket', 'srcset', default_value='URL')
|
||||||
case 87:
|
elif index == 87:
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
||||||
case 93:
|
elif index == 93:
|
||||||
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'headers' , default_value='header_id')
|
self.add_input('LnxStringSocket', 'headers' , default_value='header_id')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
case 95:
|
elif index == 95:
|
||||||
self.add_input('LnxStringSocket', 'cols' , default_value='number')
|
self.add_input('LnxStringSocket', 'cols' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir')
|
self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
@ -283,18 +283,18 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'placeholder' , default_value='text')
|
self.add_input('LnxStringSocket', 'placeholder' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'rows' , default_value='number')
|
self.add_input('LnxStringSocket', 'rows' , default_value='number')
|
||||||
case 97:
|
elif index == 97:
|
||||||
self.add_input('LnxStringSocket', 'abbr' , default_value='text')
|
self.add_input('LnxStringSocket', 'abbr' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'headers', default_value='header_id')
|
self.add_input('LnxStringSocket', 'headers', default_value='header_id')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
case 99:
|
elif index == 99:
|
||||||
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
||||||
case 102:
|
elif index == 102:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'srclang', default_value='en')
|
self.add_input('LnxStringSocket', 'srclang', default_value='en')
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
case 106:
|
elif index == 106:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
||||||
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
||||||
|
@ -38,12 +38,11 @@ class JSEventTargetNode(LnxLogicTreeNode):
|
|||||||
# Arguements for type Client
|
# Arguements for type Client
|
||||||
index = self.get_count_in(select_current)
|
index = self.get_count_in(select_current)
|
||||||
|
|
||||||
match index:
|
if index == 2:
|
||||||
case 2:
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'JS Object')
|
self.add_input('LnxDynamicSocket', 'JS Object')
|
||||||
self.add_input('LnxDynamicSocket', 'Event')
|
self.add_input('LnxDynamicSocket', 'Event')
|
||||||
case _:
|
else:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'JS Object')
|
self.add_input('LnxDynamicSocket', 'JS Object')
|
||||||
self.add_input('LnxStringSocket', 'Type')
|
self.add_input('LnxStringSocket', 'Type')
|
||||||
|
@ -43,24 +43,23 @@ class RenderElementNode(LnxLogicTreeNode):
|
|||||||
# Arguements for type Client
|
# Arguements for type Client
|
||||||
index = self.get_count_in(select_current)
|
index = self.get_count_in(select_current)
|
||||||
|
|
||||||
match index:
|
if index == 2:
|
||||||
case 2:
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Torrent')
|
self.add_input('LnxDynamicSocket', 'Torrent')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
case 5:
|
elif index == 5:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'HTML')
|
self.add_input('LnxStringSocket', 'HTML')
|
||||||
case 6:
|
elif index == 6:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'Text')
|
self.add_input('LnxStringSocket', 'Text')
|
||||||
case 7:
|
elif index == 7:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxStringSocket', 'HTML')
|
self.add_input('LnxStringSocket', 'HTML')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
case _:
|
else:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
|
@ -66,7 +66,10 @@ class LnxGroupTree(bpy.types.NodeTree):
|
|||||||
"""Try to avoid creating loops of group trees with each other"""
|
"""Try to avoid creating loops of group trees with each other"""
|
||||||
# upstream trees of tested treed should nad share trees with downstream trees of current tree
|
# upstream trees of tested treed should nad share trees with downstream trees of current tree
|
||||||
tested_tree_upstream_trees = {t.name for t in self.upstream_trees()}
|
tested_tree_upstream_trees = {t.name for t in self.upstream_trees()}
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
current_tree_downstream_trees = {p.node_tree.name for p in bpy.context.space_data.path}
|
current_tree_downstream_trees = {p.node_tree.name for p in bpy.context.space_data.path}
|
||||||
|
else:
|
||||||
|
current_tree_downstream_trees = set()
|
||||||
shared_trees = tested_tree_upstream_trees & current_tree_downstream_trees
|
shared_trees = tested_tree_upstream_trees & current_tree_downstream_trees
|
||||||
return not shared_trees
|
return not shared_trees
|
||||||
|
|
||||||
|
@ -2,9 +2,17 @@ from collections import OrderedDict
|
|||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any, final, Generator, List, Optional, Type, Union
|
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
|
||||||
from typing import OrderedDict as ODict # Prevent naming conflicts
|
from typing import OrderedDict as ODict # Prevent naming conflicts
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import final
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.8 compatibility
|
||||||
|
def final(f):
|
||||||
|
"""No final in Python < 3.8"""
|
||||||
|
return f
|
||||||
|
|
||||||
import bpy.types
|
import bpy.types
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
from nodeitems_utils import NodeItem
|
from nodeitems_utils import NodeItem
|
||||||
@ -39,11 +47,11 @@ PKG_AS_CATEGORY = "__pkgcat__"
|
|||||||
nodes = []
|
nodes = []
|
||||||
category_items: ODict[str, List['LnxNodeCategory']] = OrderedDict()
|
category_items: ODict[str, List['LnxNodeCategory']] = OrderedDict()
|
||||||
|
|
||||||
array_nodes: dict[str, 'LnxLogicTreeNode'] = dict()
|
array_nodes: Dict[str, 'LnxLogicTreeNode'] = dict()
|
||||||
|
|
||||||
# See LnxLogicTreeNode.update()
|
# See LnxLogicTreeNode.update()
|
||||||
# format: [tree pointer => (num inputs, num input links, num outputs, num output links)]
|
# format: [tree pointer => (num inputs, num input links, num outputs, num output links)]
|
||||||
last_node_state: dict[int, tuple[int, int, int, int]] = {}
|
last_node_state: Dict[int, Tuple[int, int, int, int]] = {}
|
||||||
|
|
||||||
|
|
||||||
class LnxLogicTreeNode(bpy.types.Node):
|
class LnxLogicTreeNode(bpy.types.Node):
|
||||||
|
@ -10,7 +10,7 @@ mutable (common Python pitfall, be aware of this!), but because they
|
|||||||
don't get accessed later it doesn't matter here and we keep it this way
|
don't get accessed later it doesn't matter here and we keep it this way
|
||||||
for parity with the Blender API.
|
for parity with the Blender API.
|
||||||
"""
|
"""
|
||||||
from typing import Any, Callable, Sequence, Union
|
from typing import Any, Callable, List, Sequence, Set, Union
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import bpy
|
import bpy
|
||||||
@ -50,6 +50,10 @@ def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any:
|
|||||||
if 'tags' in kwargs:
|
if 'tags' in kwargs:
|
||||||
del kwargs['tags']
|
del kwargs['tags']
|
||||||
|
|
||||||
|
# Remove override parameter for Blender versions that don't support it
|
||||||
|
if bpy.app.version < (2, 90, 0) and 'override' in kwargs:
|
||||||
|
del kwargs['override']
|
||||||
|
|
||||||
return prop_type(*args, **kwargs)
|
return prop_type(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +91,7 @@ def HaxeBoolVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.BoolProperty']:
|
) -> List['bpy.types.BoolProperty']:
|
||||||
"""Declares a new BoolVectorProperty that has a Haxe counterpart
|
"""Declares a new BoolVectorProperty that has a Haxe counterpart
|
||||||
with the given prop_name (Python and Haxe names must be identical
|
with the given prop_name (Python and Haxe names must be identical
|
||||||
for now).
|
for now).
|
||||||
@ -118,7 +122,7 @@ def HaxeEnumProperty(
|
|||||||
items: Sequence,
|
items: Sequence,
|
||||||
name: str = "",
|
name: str = "",
|
||||||
description: str = "",
|
description: str = "",
|
||||||
default: Union[str, set[str]] = None,
|
default: Union[str, Set[str]] = None,
|
||||||
options: set = {'ANIMATABLE'},
|
options: set = {'ANIMATABLE'},
|
||||||
override: set = set(),
|
override: set = set(),
|
||||||
tags: set = set(),
|
tags: set = set(),
|
||||||
@ -180,7 +184,7 @@ def HaxeFloatVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.FloatProperty']:
|
) -> List['bpy.types.FloatProperty']:
|
||||||
"""Declares a new FloatVectorProperty that has a Haxe counterpart
|
"""Declares a new FloatVectorProperty that has a Haxe counterpart
|
||||||
with the given prop_name (Python and Haxe names must be identical
|
with the given prop_name (Python and Haxe names must be identical
|
||||||
for now).
|
for now).
|
||||||
@ -232,7 +236,7 @@ def HaxeIntVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.IntProperty']:
|
) -> List['bpy.types.IntProperty']:
|
||||||
"""Declares a new IntVectorProperty that has a Haxe counterpart with
|
"""Declares a new IntVectorProperty that has a Haxe counterpart with
|
||||||
the given prop_name (Python and Haxe names must be identical for now).
|
the given prop_name (Python and Haxe names must be identical for now).
|
||||||
"""
|
"""
|
||||||
|
@ -27,7 +27,10 @@ class GroupInputsNode(LnxLogicTreeNode):
|
|||||||
copy_override: BoolProperty(name='copy override', description='', default=False)
|
copy_override: BoolProperty(name='copy override', description='', default=False)
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.edit_tree
|
tree = bpy.context.space_data.edit_tree
|
||||||
|
else:
|
||||||
|
return
|
||||||
node_count = 0
|
node_count = 0
|
||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
if node.bl_idname == 'LNGroupInputsNode':
|
if node.bl_idname == 'LNGroupInputsNode':
|
||||||
|
@ -27,7 +27,10 @@ class GroupOutputsNode(LnxLogicTreeNode):
|
|||||||
copy_override: BoolProperty(name='copy override', description='', default=False)
|
copy_override: BoolProperty(name='copy override', description='', default=False)
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.edit_tree
|
tree = bpy.context.space_data.edit_tree
|
||||||
|
else:
|
||||||
|
return
|
||||||
node_count = 0
|
node_count = 0
|
||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
if node.bl_idname == 'LNGroupOutputsNode':
|
if node.bl_idname == 'LNGroupOutputsNode':
|
||||||
|
@ -350,7 +350,10 @@ class LNX_PG_TreeVarListItem(bpy.types.PropertyGroup):
|
|||||||
def _set_name(self, value: str):
|
def _set_name(self, value: str):
|
||||||
old_name = self._get_name()
|
old_name = self._get_name()
|
||||||
|
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.path[-1].node_tree
|
tree = bpy.context.space_data.path[-1].node_tree
|
||||||
|
else:
|
||||||
|
return # No valid context
|
||||||
lst = tree.lnx_treevariableslist
|
lst = tree.lnx_treevariableslist
|
||||||
|
|
||||||
if value == '':
|
if value == '':
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Optional, TextIO
|
from typing import List, Optional, TextIO, Dict, Any, TypeVar, TYPE_CHECKING
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -17,14 +17,14 @@ if lnx.is_reload(__name__):
|
|||||||
else:
|
else:
|
||||||
lnx.enable_reload(__name__)
|
lnx.enable_reload(__name__)
|
||||||
|
|
||||||
parsed_nodes = []
|
parsed_nodes = [] # type: List[str]
|
||||||
parsed_ids = dict() # Sharing node data
|
parsed_ids = dict() # type: Dict[str, str] # Sharing node data
|
||||||
function_nodes = dict()
|
function_nodes = dict() # type: Dict[str, Any]
|
||||||
function_node_outputs = dict()
|
function_node_outputs = dict() # type: Dict[str, str]
|
||||||
group_name = ''
|
group_name = ''
|
||||||
|
|
||||||
|
|
||||||
def get_logic_trees() -> list['lnx.nodes_logic.LnxLogicTree']:
|
def get_logic_trees() -> List['lnx.nodes_logic.LnxLogicTree']:
|
||||||
ar = []
|
ar = []
|
||||||
for node_group in bpy.data.node_groups:
|
for node_group in bpy.data.node_groups:
|
||||||
if node_group.bl_idname == 'LnxLogicTreeType':
|
if node_group.bl_idname == 'LnxLogicTreeType':
|
||||||
@ -140,7 +140,7 @@ def build_node_group_tree(node_group: 'lnx.nodes_logic.LnxLogicTree', f: TextIO,
|
|||||||
return group_input_name, group_output_name
|
return group_input_name, group_output_name
|
||||||
|
|
||||||
|
|
||||||
def build_node(node: bpy.types.Node, f: TextIO, name_prefix: str = None) -> Optional[str]:
|
def build_node(node: bpy.types.Node, f: TextIO, name_prefix: Optional[str] = None) -> Optional[str]:
|
||||||
"""Builds the given node and returns its name. f is an opened file object."""
|
"""Builds the given node and returns its name. f is an opened file object."""
|
||||||
global parsed_nodes
|
global parsed_nodes
|
||||||
global parsed_ids
|
global parsed_ids
|
||||||
|
@ -76,7 +76,7 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket,
|
|||||||
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2)
|
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2)
|
||||||
|
|
||||||
|
|
||||||
if bpy.app.version < (3, 0, 0):
|
if bpy.app.version < (2, 92, 0):
|
||||||
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
||||||
if state.parse_surface:
|
if state.parse_surface:
|
||||||
c.write_normal(node.inputs[20])
|
c.write_normal(node.inputs[20])
|
||||||
@ -84,18 +84,20 @@ if bpy.app.version < (3, 0, 0):
|
|||||||
state.out_metallic = c.parse_value_input(node.inputs[4])
|
state.out_metallic = c.parse_value_input(node.inputs[4])
|
||||||
state.out_specular = c.parse_value_input(node.inputs[5])
|
state.out_specular = c.parse_value_input(node.inputs[5])
|
||||||
state.out_roughness = c.parse_value_input(node.inputs[7])
|
state.out_roughness = c.parse_value_input(node.inputs[7])
|
||||||
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
|
if node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False):
|
||||||
and (node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False)):
|
|
||||||
emission_col = c.parse_vector_input(node.inputs[17])
|
emission_col = c.parse_vector_input(node.inputs[17])
|
||||||
emission_strength = c.parse_value_input(node.inputs[18])
|
state.out_emission_col = emission_col
|
||||||
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
|
|
||||||
mat_state.emission_type = mat_state.EmissionType.SHADED
|
mat_state.emission_type = mat_state.EmissionType.SHADED
|
||||||
else:
|
else:
|
||||||
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
||||||
if state.parse_opacity:
|
if state.parse_opacity:
|
||||||
state.out_ior = c.parse_value_input(node.inputs[14])
|
state.out_ior = c.parse_value_input(node.inputs[14])
|
||||||
state.out_opacity = c.parse_value_input(node.inputs[19])
|
# In Blender 2.83, Alpha socket is at index 18, not 19
|
||||||
if bpy.app.version >= (3, 0, 0) and bpy.app.version <= (4, 1, 0):
|
if 'Alpha' in node.inputs:
|
||||||
|
state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
|
||||||
|
else:
|
||||||
|
state.out_opacity = '1.0'
|
||||||
|
if bpy.app.version >= (2, 92, 0) and bpy.app.version <= (4, 1, 0):
|
||||||
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
||||||
if state.parse_surface:
|
if state.parse_surface:
|
||||||
c.write_normal(node.inputs[22])
|
c.write_normal(node.inputs[22])
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -32,8 +32,8 @@ else:
|
|||||||
is_displacement = False
|
is_displacement = False
|
||||||
|
|
||||||
# User callbacks
|
# User callbacks
|
||||||
write_material_attribs: Optional[Callable[[dict[str, Any], shader.Shader], bool]] = None
|
write_material_attribs: Optional[Callable[[Dict[str, Any], shader.Shader], bool]] = None
|
||||||
write_material_attribs_post: Optional[Callable[[dict[str, Any], shader.Shader], None]] = None
|
write_material_attribs_post: Optional[Callable[[Dict[str, Any], shader.Shader], None]] = None
|
||||||
write_vertex_attribs: Optional[Callable[[shader.Shader], bool]] = None
|
write_vertex_attribs: Optional[Callable[[shader.Shader], bool]] = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,8 +169,7 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('float s = sin(p_angle);')
|
vert.write('float s = sin(p_angle);')
|
||||||
vert.write('vec3 center = spos.xyz - p_location;')
|
vert.write('vec3 center = spos.xyz - p_location;')
|
||||||
|
|
||||||
match rotation_mode:
|
if rotation_mode == 'OB_X':
|
||||||
case 'OB_X':
|
|
||||||
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
||||||
vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);')
|
vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);')
|
||||||
vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;')
|
vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;')
|
||||||
@ -179,13 +178,13 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
|
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
|
||||||
vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);')
|
vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);')
|
||||||
vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));')
|
vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));')
|
||||||
case 'OB_Y':
|
elif rotation_mode == 'OB_Y':
|
||||||
vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);')
|
vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);')
|
||||||
vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;')
|
vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;')
|
||||||
|
|
||||||
if (not shadowmap):
|
if (not shadowmap):
|
||||||
vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));')
|
vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));')
|
||||||
case 'OB_Z':
|
elif rotation_mode == 'OB_Z':
|
||||||
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
||||||
vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);')
|
vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);')
|
||||||
vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);')
|
vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);')
|
||||||
@ -196,7 +195,7 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);')
|
vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);')
|
||||||
vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);')
|
vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);')
|
||||||
vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));')
|
vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));')
|
||||||
case 'VEL':
|
elif rotation_mode == 'VEL':
|
||||||
vert.write('vec3 forward = -normalize(p_velocity);')
|
vert.write('vec3 forward = -normalize(p_velocity);')
|
||||||
vert.write('if (length(forward) > 1e-5) {')
|
vert.write('if (length(forward) > 1e-5) {')
|
||||||
vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);')
|
vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Generator
|
from typing import Generator, Tuple
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ def iter_nodes_leenkxpbr(node_group: bpy.types.NodeTree) -> Generator[bpy.types.
|
|||||||
yield node
|
yield node
|
||||||
|
|
||||||
|
|
||||||
def equals_color_socket(socket: bpy.types.NodeSocketColor, value: tuple[float, ...], *, comp_alpha=True) -> bool:
|
def equals_color_socket(socket: bpy.types.NodeSocketColor, value: Tuple[float, ...], *, comp_alpha=True) -> bool:
|
||||||
# NodeSocketColor.default_value is of bpy_prop_array type that doesn't
|
# NodeSocketColor.default_value is of bpy_prop_array type that doesn't
|
||||||
# support direct comparison
|
# support direct comparison
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,7 @@ This module contains a list of all material nodes that Leenkx supports
|
|||||||
"""
|
"""
|
||||||
from enum import IntEnum, unique
|
from enum import IntEnum, unique
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional, Dict, List, Tuple, TypeVar, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class MaterialNodeMeta:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ALL_NODES: dict[str, MaterialNodeMeta] = {
|
ALL_NODES: Dict[str, MaterialNodeMeta] = {
|
||||||
# --- nodes_color
|
# --- nodes_color
|
||||||
'BRIGHTCONTRAST': MaterialNodeMeta(parse_func=nodes_color.parse_brightcontrast),
|
'BRIGHTCONTRAST': MaterialNodeMeta(parse_func=nodes_color.parse_brightcontrast),
|
||||||
'CURVE_RGB': MaterialNodeMeta(parse_func=nodes_color.parse_curvergb),
|
'CURVE_RGB': MaterialNodeMeta(parse_func=nodes_color.parse_curvergb),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import collections.abc
|
import collections.abc
|
||||||
from typing import Any, Generator, Optional, Type, Union
|
from typing import Any, Generator, Optional, Type, Tuple, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
@ -49,7 +49,7 @@ def iter_nodes_by_type(node_group: bpy.types.NodeTree, ntype: str) -> Generator[
|
|||||||
yield node
|
yield node
|
||||||
|
|
||||||
|
|
||||||
def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> Tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
||||||
"""Get the node and the output socket of that node that is connected
|
"""Get the node and the output socket of that node that is connected
|
||||||
to the given input, while following reroutes. If the input has
|
to the given input, while following reroutes. If the input has
|
||||||
multiple incoming connections, the first one is followed. If the
|
multiple incoming connections, the first one is followed. If the
|
||||||
@ -70,7 +70,7 @@ def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> tuple[Option
|
|||||||
return from_node, link.from_socket
|
return from_node, link.from_socket
|
||||||
|
|
||||||
|
|
||||||
def output_get_connected_node(output_socket: bpy.types.NodeSocket) -> tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
def output_get_connected_node(output_socket: bpy.types.NodeSocket) -> Tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
||||||
"""Get the node and the input socket of that node that is connected
|
"""Get the node and the input socket of that node that is connected
|
||||||
to the given output, while following reroutes. If the output has
|
to the given output, while following reroutes. If the output has
|
||||||
multiple outgoing connections, the first one is followed. If the
|
multiple outgoing connections, the first one is followed. If the
|
||||||
@ -152,7 +152,7 @@ def get_export_node_name(node: bpy.types.Node) -> str:
|
|||||||
return '_' + lnx.utils.safesrc(node.name)
|
return '_' + lnx.utils.safesrc(node.name)
|
||||||
|
|
||||||
|
|
||||||
def get_haxe_property_names(node: bpy.types.Node) -> Generator[tuple[str, str], None, None]:
|
def get_haxe_property_names(node: bpy.types.Node) -> Generator[Tuple[str, str], None, None]:
|
||||||
"""Generator that yields the names of all node properties that have
|
"""Generator that yields the names of all node properties that have
|
||||||
a counterpart in the node's Haxe class.
|
a counterpart in the node's Haxe class.
|
||||||
"""
|
"""
|
||||||
|
@ -477,6 +477,7 @@ __REG_CLASSES = (
|
|||||||
LnxOpenNodeWikiEntry,
|
LnxOpenNodeWikiEntry,
|
||||||
LNX_OT_ReplaceNodesOperator,
|
LNX_OT_ReplaceNodesOperator,
|
||||||
LNX_OT_RecalculateRotations,
|
LNX_OT_RecalculateRotations,
|
||||||
|
LNX_MT_NodeAddOverride,
|
||||||
LNX_OT_AddNodeOverride,
|
LNX_OT_AddNodeOverride,
|
||||||
LNX_UL_InterfaceSockets,
|
LNX_UL_InterfaceSockets,
|
||||||
LNX_PT_LogicNodePanel,
|
LNX_PT_LogicNodePanel,
|
||||||
@ -491,9 +492,8 @@ def register():
|
|||||||
lnx.logicnode.lnx_node_group.register()
|
lnx.logicnode.lnx_node_group.register()
|
||||||
lnx.logicnode.tree_variables.register()
|
lnx.logicnode.tree_variables.register()
|
||||||
|
|
||||||
# Store original draw method and restore during unregister
|
LNX_MT_NodeAddOverride.overridden_menu = bpy.types.NODE_MT_add
|
||||||
LNX_MT_NodeAddOverride.overridden_draw = bpy.types.NODE_MT_add.draw
|
LNX_MT_NodeAddOverride.overridden_draw = bpy.types.NODE_MT_add.draw
|
||||||
bpy.types.NODE_MT_add.draw = LNX_MT_NodeAddOverride.draw
|
|
||||||
|
|
||||||
__reg_classes()
|
__reg_classes()
|
||||||
|
|
||||||
@ -508,11 +508,8 @@ def unregister():
|
|||||||
# Ensure that globals are reset if the addon is enabled again in the same Blender session
|
# Ensure that globals are reset if the addon is enabled again in the same Blender session
|
||||||
lnx_nodes.reset_globals()
|
lnx_nodes.reset_globals()
|
||||||
|
|
||||||
# Restore original draw method
|
|
||||||
if hasattr(LNX_MT_NodeAddOverride, 'overridden_draw'):
|
|
||||||
bpy.types.NODE_MT_add.draw = LNX_MT_NodeAddOverride.overridden_draw
|
|
||||||
|
|
||||||
__unreg_classes()
|
__unreg_classes()
|
||||||
|
bpy.utils.register_class(LNX_MT_NodeAddOverride.overridden_menu)
|
||||||
|
|
||||||
lnx.logicnode.tree_variables.unregister()
|
lnx.logicnode.tree_variables.unregister()
|
||||||
lnx.logicnode.lnx_node_group.unregister()
|
lnx.logicnode.lnx_node_group.unregister()
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
|
|
||||||
|
# Helper function to handle version compatibility
|
||||||
|
def compatible_prop(prop_func, **kwargs):
|
||||||
|
"""Create properties compatible with multiple Blender versions."""
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
# Remove override parameter for Blender 2.83
|
||||||
|
kwargs.pop('override', None)
|
||||||
|
return prop_func(**kwargs)
|
||||||
import re
|
import re
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|
||||||
@ -341,7 +349,7 @@ def init_properties():
|
|||||||
bpy.types.World.lnx_winmaximize = BoolProperty(name="Maximizable", description="Allow window maximize", default=False, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_winmaximize = BoolProperty(name="Maximizable", description="Allow window maximize", default=False, update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.lnx_winminimize = BoolProperty(name="Minimizable", description="Allow window minimize", default=True, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_winminimize = BoolProperty(name="Minimizable", description="Allow window minimize", default=True, update=assets.invalidate_compiler_cache)
|
||||||
# For object
|
# For object
|
||||||
bpy.types.Object.lnx_instanced = EnumProperty(
|
bpy.types.Object.lnx_instanced = compatible_prop(EnumProperty,
|
||||||
items = [('Off', 'Off', 'No instancing of children'),
|
items = [('Off', 'Off', 'No instancing of children'),
|
||||||
('Loc', 'Loc', 'Instances use their unique position (ipos)'),
|
('Loc', 'Loc', 'Instances use their unique position (ipos)'),
|
||||||
('Loc + Rot', 'Loc + Rot', 'Instances use their unique position and rotation (ipos and irot)'),
|
('Loc + Rot', 'Loc + Rot', 'Instances use their unique position and rotation (ipos and irot)'),
|
||||||
@ -351,12 +359,12 @@ def init_properties():
|
|||||||
description='Whether to use instancing to draw the children of this object. If enabled, this option defines what attributes may vary between the instances',
|
description='Whether to use instancing to draw the children of this object. If enabled, this option defines what attributes may vary between the instances',
|
||||||
update=assets.invalidate_instance_cache,
|
update=assets.invalidate_instance_cache,
|
||||||
override={'LIBRARY_OVERRIDABLE'})
|
override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_export = BoolProperty(name="Export", description="Export object data", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_export = compatible_prop(BoolProperty, name="Export", description="Export object data", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_sorting_index = IntProperty(name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_spawn = BoolProperty(name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_mobile = BoolProperty(name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_mobile = compatible_prop(BoolProperty, name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_visible = BoolProperty(name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_visible_shadow = BoolProperty(name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
||||||
bpy.types.Object.lnx_rb_linear_factor = FloatVectorProperty(name="Linear Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
bpy.types.Object.lnx_rb_linear_factor = FloatVectorProperty(name="Linear Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
||||||
bpy.types.Object.lnx_rb_angular_factor = FloatVectorProperty(name="Angular Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
bpy.types.Object.lnx_rb_angular_factor = FloatVectorProperty(name="Angular Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
||||||
|
@ -420,15 +420,18 @@ class LNX_OT_ExporterOpenVS(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
if not lnx.utils.get_os_is_windows():
|
if not lnx.utils.get_os_is_windows():
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('This operator is only supported on Windows')
|
cls.poll_message_set('This operator is only supported on Windows')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
if len(wrd.lnx_exporterlist) == 0:
|
if len(wrd.lnx_exporterlist) == 0:
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('No export configuration exists')
|
cls.poll_message_set('No export configuration exists')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrd.lnx_exporterlist[wrd.lnx_exporterlist_index].lnx_project_target != 'windows-hl':
|
if wrd.lnx_exporterlist[wrd.lnx_exporterlist_index].lnx_project_target != 'windows-hl':
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('This operator only works with the Windows (C) target')
|
cls.poll_message_set('This operator only works with the Windows (C) target')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -12,9 +12,18 @@ import bpy.utils.previews
|
|||||||
import lnx.make as make
|
import lnx.make as make
|
||||||
from lnx.props_traits_props import *
|
from lnx.props_traits_props import *
|
||||||
import lnx.ui_icons as ui_icons
|
import lnx.ui_icons as ui_icons
|
||||||
|
|
||||||
|
def compatible_prop(property_func, **kwargs):
|
||||||
|
"""Create properties compatible with different Blender versions."""
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
# Remove override parameter for Blender 2.83
|
||||||
|
kwargs.pop('override', None)
|
||||||
|
return property_func(**kwargs)
|
||||||
|
|
||||||
import lnx.utils
|
import lnx.utils
|
||||||
import lnx.write_data as write_data
|
import lnx.write_data as write_data
|
||||||
|
|
||||||
|
|
||||||
if lnx.is_reload(__name__):
|
if lnx.is_reload(__name__):
|
||||||
lnx.make = lnx.reload_module(lnx.make)
|
lnx.make = lnx.reload_module(lnx.make)
|
||||||
lnx.props_traits_props = lnx.reload_module(lnx.props_traits_props)
|
lnx.props_traits_props = lnx.reload_module(lnx.props_traits_props)
|
||||||
@ -91,20 +100,20 @@ class LnxTraitListItem(bpy.types.PropertyGroup):
|
|||||||
"""Ensure that only logic node trees show up as node traits"""
|
"""Ensure that only logic node trees show up as node traits"""
|
||||||
return tree.bl_idname == 'LnxLogicTreeType'
|
return tree.bl_idname == 'LnxLogicTreeType'
|
||||||
|
|
||||||
name: StringProperty(name="Name", description="The name of the trait", default="", override={"LIBRARY_OVERRIDABLE"})
|
name = compatible_prop(StringProperty, name="Name", description="The name of the trait", default="", override={"LIBRARY_OVERRIDABLE"})
|
||||||
enabled_prop: BoolProperty(name="", description="Whether this trait is enabled", default=True, update=trigger_recompile, override={"LIBRARY_OVERRIDABLE"})
|
enabled_prop = compatible_prop(BoolProperty, name="", description="Whether this trait is enabled", default=True, update=trigger_recompile, override={"LIBRARY_OVERRIDABLE"})
|
||||||
is_object: BoolProperty(name="", default=True)
|
is_object = BoolProperty(name="", default=True)
|
||||||
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False, override={"LIBRARY_OVERRIDABLE"})
|
fake_user = compatible_prop(BoolProperty, name="Fake User", description="Export this trait even if it is deactivated", default=False, override={"LIBRARY_OVERRIDABLE"})
|
||||||
type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM)
|
type_prop = EnumProperty(name="Type", items=PROP_TYPES_ENUM)
|
||||||
|
|
||||||
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
class_name_prop = compatible_prop(StringProperty, name="Class", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
canvas_name_prop: StringProperty(name="Canvas", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
canvas_name_prop = compatible_prop(StringProperty, name="Canvas", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
webassembly_prop: StringProperty(name="Module", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
webassembly_prop = compatible_prop(StringProperty, name="Module", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group, override={"LIBRARY_OVERRIDABLE"}, poll=poll_node_trees)
|
node_tree_prop = compatible_prop(PointerProperty, type=NodeTree, update=update_trait_group, override={"LIBRARY_OVERRIDABLE"}, poll=poll_node_trees)
|
||||||
|
|
||||||
lnx_traitpropslist: CollectionProperty(type=LnxTraitPropListItem)
|
lnx_traitpropslist = CollectionProperty(type=LnxTraitPropListItem)
|
||||||
lnx_traitpropslist_index: IntProperty(name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
lnx_traitpropslist_index = compatible_prop(IntProperty, name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
||||||
lnx_traitpropswarnings: CollectionProperty(type=LnxTraitPropWarning)
|
lnx_traitpropswarnings = CollectionProperty(type=LnxTraitPropWarning)
|
||||||
|
|
||||||
class LNX_UL_TraitList(bpy.types.UIList):
|
class LNX_UL_TraitList(bpy.types.UIList):
|
||||||
"""List of traits."""
|
"""List of traits."""
|
||||||
@ -475,10 +484,12 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator):
|
|||||||
# If not, append vertex
|
# If not, append vertex
|
||||||
traversed_indices.append(vertex_index)
|
traversed_indices.append(vertex_index)
|
||||||
vertex = export_mesh.vertices[vertex_index].co
|
vertex = export_mesh.vertices[vertex_index].co
|
||||||
# Apply world transform and maintain coordinate system
|
# Apply world transform
|
||||||
tv = world_matrix @ vertex
|
tv = world_matrix @ vertex
|
||||||
# Write to OBJ without flipping coordinates
|
# Write to OBJ
|
||||||
f.write("v %.4f %.4f %.4f\n" % (tv[0], tv[1], tv[2]))
|
f.write("v %.4f " % (tv[0]))
|
||||||
|
f.write("%.4f " % (tv[2]))
|
||||||
|
f.write("%.4f\n" % (tv[1])) # Flipped
|
||||||
|
|
||||||
# Max index of this object
|
# Max index of this object
|
||||||
max_index = 0
|
max_index = 0
|
||||||
@ -522,10 +533,8 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator):
|
|||||||
|
|
||||||
# NavMesh preview settings, cleanup
|
# NavMesh preview settings, cleanup
|
||||||
navmesh.name = nav_mesh_name
|
navmesh.name = nav_mesh_name
|
||||||
# Match the original object's transform
|
navmesh.rotation_euler = (0, 0, 0)
|
||||||
navmesh.location = obj.location
|
navmesh.location = (0, 0, 0)
|
||||||
navmesh.rotation_euler = obj.rotation_euler
|
|
||||||
navmesh.scale = (1, 1, 1) # Reset scale to avoid distortion
|
|
||||||
navmesh.lnx_export = False
|
navmesh.lnx_export = False
|
||||||
|
|
||||||
bpy.context.view_layer.objects.active = navmesh
|
bpy.context.view_layer.objects.active = navmesh
|
||||||
@ -756,6 +765,7 @@ class LnxRefreshObjectScriptsButton(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set(LnxRefreshScriptsButton.poll_msg)
|
cls.poll_message_set(LnxRefreshScriptsButton.poll_msg)
|
||||||
# Technically we could keep the operator enabled here since
|
# Technically we could keep the operator enabled here since
|
||||||
# fetch_trait_props() checks for overrides and the operator does
|
# fetch_trait_props() checks for overrides and the operator does
|
||||||
@ -1064,11 +1074,10 @@ __REG_CLASSES = (
|
|||||||
)
|
)
|
||||||
__reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES)
|
__reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES)
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
__reg_classes()
|
__reg_classes()
|
||||||
|
|
||||||
bpy.types.Object.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
bpy.types.Object.lnx_traitlist = compatible_prop(CollectionProperty, type=LnxTraitListItem)
|
||||||
bpy.types.Object.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
bpy.types.Object.lnx_traitlist_index = compatible_prop(IntProperty, name="Index for lnx_traitlist", default=0)
|
||||||
bpy.types.Scene.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
bpy.types.Scene.lnx_traitlist = compatible_prop(CollectionProperty, type=LnxTraitListItem)
|
||||||
bpy.types.Scene.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
bpy.types.Scene.lnx_traitlist_index = compatible_prop(IntProperty, name="Index for lnx_traitlist", default=0)
|
||||||
|
@ -3,6 +3,14 @@ from bpy.props import *
|
|||||||
|
|
||||||
__all__ = ['LnxTraitPropWarning', 'LnxTraitPropListItem', 'LNX_UL_PropList']
|
__all__ = ['LnxTraitPropWarning', 'LnxTraitPropListItem', 'LNX_UL_PropList']
|
||||||
|
|
||||||
|
# Helper function to handle version compatibility
|
||||||
|
def compatible_prop(prop_func, **kwargs):
|
||||||
|
"""Create properties compatible with both Blender 2.83 and newer versions."""
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
# Remove override parameter for Blender 2.83
|
||||||
|
kwargs.pop('override', None)
|
||||||
|
return prop_func(**kwargs)
|
||||||
|
|
||||||
PROP_TYPE_ICONS = {
|
PROP_TYPE_ICONS = {
|
||||||
"String": "SORTALPHA",
|
"String": "SORTALPHA",
|
||||||
"Int": "CHECKBOX_DEHLT",
|
"Int": "CHECKBOX_DEHLT",
|
||||||
@ -35,18 +43,19 @@ def filter_objects(item, b_object):
|
|||||||
|
|
||||||
|
|
||||||
class LnxTraitPropWarning(bpy.types.PropertyGroup):
|
class LnxTraitPropWarning(bpy.types.PropertyGroup):
|
||||||
propName: StringProperty(name="Property Name")
|
propName = compatible_prop(StringProperty, name="Property Name", override={"LIBRARY_OVERRIDABLE"})
|
||||||
warning: StringProperty(name="Warning")
|
warning = compatible_prop(StringProperty, name="Warning", override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
|
|
||||||
class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
||||||
"""Group of properties representing an item in the list."""
|
"""Group of properties representing an item in the list."""
|
||||||
name: StringProperty(
|
name = compatible_prop(StringProperty,
|
||||||
name="Name",
|
name="Name",
|
||||||
description="The name of this property",
|
description="The name of this property",
|
||||||
default="Untitled")
|
default="Untitled",
|
||||||
|
override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
type: EnumProperty(
|
type = compatible_prop(EnumProperty,
|
||||||
items=(
|
items=(
|
||||||
# (Haxe Type, Display Name, Description)
|
# (Haxe Type, Display Name, Description)
|
||||||
("String", "String", "String Type"),
|
("String", "String", "String Type"),
|
||||||
@ -69,18 +78,18 @@ class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# === VALUES ===
|
# === VALUES ===
|
||||||
value_string: StringProperty(name="Value", default="", override={"LIBRARY_OVERRIDABLE"})
|
value_string = compatible_prop(StringProperty, name="Value", default="", override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_int: IntProperty(name="Value", default=0, override={"LIBRARY_OVERRIDABLE"})
|
value_int = compatible_prop(IntProperty, name="Value", default=0, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_float: FloatProperty(name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"})
|
value_float = compatible_prop(FloatProperty, name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_bool: BoolProperty(name="Value", default=False, override={"LIBRARY_OVERRIDABLE"})
|
value_bool = compatible_prop(BoolProperty, name="Value", default=False, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_vec2: FloatVectorProperty(name="Value", size=2, override={"LIBRARY_OVERRIDABLE"})
|
value_vec2 = compatible_prop(FloatVectorProperty, name="Value", size=2, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_vec3: FloatVectorProperty(name="Value", size=3, override={"LIBRARY_OVERRIDABLE"})
|
value_vec3 = compatible_prop(FloatVectorProperty, name="Value", size=3, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_vec4: FloatVectorProperty(name="Value", size=4, override={"LIBRARY_OVERRIDABLE"})
|
value_vec4 = compatible_prop(FloatVectorProperty, name="Value", size=4, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_object: PointerProperty(
|
value_object = compatible_prop(PointerProperty,
|
||||||
name="Value", type=bpy.types.Object, poll=filter_objects,
|
name="Value", type=bpy.types.Object, poll=filter_objects,
|
||||||
override={"LIBRARY_OVERRIDABLE"}
|
override={"LIBRARY_OVERRIDABLE"}
|
||||||
)
|
)
|
||||||
value_scene: PointerProperty(name="Value", type=bpy.types.Scene, override={"LIBRARY_OVERRIDABLE"})
|
value_scene = compatible_prop(PointerProperty, name="Value", type=bpy.types.Scene, override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
def set_value(self, val):
|
def set_value(self, val):
|
||||||
# Would require way too much effort, so it's out of scope here.
|
# Would require way too much effort, so it's out of scope here.
|
||||||
|
@ -8,6 +8,24 @@ import mathutils
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
|
|
||||||
|
# Helper functions for Blender version compatibility
|
||||||
|
def get_panel_options():
|
||||||
|
"""Get panel options compatible with current Blender version."""
|
||||||
|
if bpy.app.version >= (2, 93, 0): # INSTANCED was introduced around 2.93
|
||||||
|
return {'INSTANCED'}
|
||||||
|
else:
|
||||||
|
return set() # Empty set for older versions
|
||||||
|
|
||||||
|
def column_with_heading(layout, heading='', align=False):
|
||||||
|
"""Create a column with optional heading, compatible across Blender versions."""
|
||||||
|
if bpy.app.version >= (2, 92, 0):
|
||||||
|
return layout.column(heading=heading, align=align)
|
||||||
|
else:
|
||||||
|
col = layout.column(align=align)
|
||||||
|
if heading:
|
||||||
|
col.label(text=heading)
|
||||||
|
return col
|
||||||
|
|
||||||
from lnx.lightmapper.panels import scene
|
from lnx.lightmapper.panels import scene
|
||||||
|
|
||||||
import lnx.api
|
import lnx.api
|
||||||
@ -939,13 +957,13 @@ class LNX_PT_LeenkxExporterPanel(bpy.types.Panel):
|
|||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(wrd, 'lnx_project_icon')
|
col.prop(wrd, 'lnx_project_icon')
|
||||||
|
|
||||||
col = layout.column(heading='Code Output', align=True)
|
col = column_with_heading(layout, 'Code Output', align=True)
|
||||||
col.prop(wrd, 'lnx_dce')
|
col.prop(wrd, 'lnx_dce')
|
||||||
col.prop(wrd, 'lnx_compiler_inline')
|
col.prop(wrd, 'lnx_compiler_inline')
|
||||||
col.prop(wrd, 'lnx_minify_js')
|
col.prop(wrd, 'lnx_minify_js')
|
||||||
col.prop(wrd, 'lnx_no_traces')
|
col.prop(wrd, 'lnx_no_traces')
|
||||||
|
|
||||||
col = layout.column(heading='Data', align=True)
|
col = column_with_heading(layout, 'Data', align=True)
|
||||||
col.prop(wrd, 'lnx_minimize')
|
col.prop(wrd, 'lnx_minimize')
|
||||||
col.prop(wrd, 'lnx_optimize_data')
|
col.prop(wrd, 'lnx_optimize_data')
|
||||||
col.prop(wrd, 'lnx_asset_compression')
|
col.prop(wrd, 'lnx_asset_compression')
|
||||||
@ -1178,32 +1196,32 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
|
|||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
|
||||||
col = layout.column(heading='Debug', align=True)
|
col = column_with_heading(layout, 'Debug', align=True)
|
||||||
col.prop(wrd, 'lnx_verbose_output')
|
col.prop(wrd, 'lnx_verbose_output')
|
||||||
col.prop(wrd, 'lnx_cache_build')
|
col.prop(wrd, 'lnx_cache_build')
|
||||||
col.prop(wrd, 'lnx_clear_on_compile')
|
col.prop(wrd, 'lnx_clear_on_compile')
|
||||||
col.prop(wrd, 'lnx_assert_level')
|
col.prop(wrd, 'lnx_assert_level')
|
||||||
col.prop(wrd, 'lnx_assert_quit')
|
col.prop(wrd, 'lnx_assert_quit')
|
||||||
|
|
||||||
col = layout.column(heading='Runtime', align=True)
|
col = column_with_heading(layout, 'Runtime', align=True)
|
||||||
col.prop(wrd, 'lnx_live_patch')
|
col.prop(wrd, 'lnx_live_patch')
|
||||||
col.prop(wrd, 'lnx_stream_scene')
|
col.prop(wrd, 'lnx_stream_scene')
|
||||||
col.prop(wrd, 'lnx_loadscreen')
|
col.prop(wrd, 'lnx_loadscreen')
|
||||||
col.prop(wrd, 'lnx_write_config')
|
col.prop(wrd, 'lnx_write_config')
|
||||||
|
|
||||||
col = layout.column(heading='Renderer', align=True)
|
col = column_with_heading(layout, 'Renderer', align=True)
|
||||||
col.prop(wrd, 'lnx_batch_meshes')
|
col.prop(wrd, 'lnx_batch_meshes')
|
||||||
col.prop(wrd, 'lnx_batch_materials')
|
col.prop(wrd, 'lnx_batch_materials')
|
||||||
col.prop(wrd, 'lnx_deinterleaved_buffers')
|
col.prop(wrd, 'lnx_deinterleaved_buffers')
|
||||||
col.prop(wrd, 'lnx_export_tangents')
|
col.prop(wrd, 'lnx_export_tangents')
|
||||||
|
|
||||||
col = layout.column(heading='Quality')
|
col = column_with_heading(layout, 'Quality')
|
||||||
row = col.row() # To expand below property UI horizontally
|
row = col.row() # To expand below property UI horizontally
|
||||||
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
||||||
col.prop(wrd, 'lnx_texture_quality')
|
col.prop(wrd, 'lnx_texture_quality')
|
||||||
col.prop(wrd, 'lnx_sound_quality')
|
col.prop(wrd, 'lnx_sound_quality')
|
||||||
|
|
||||||
col = layout.column(heading='External Assets')
|
col = column_with_heading(layout, 'External Assets')
|
||||||
col.prop(wrd, 'lnx_copy_override')
|
col.prop(wrd, 'lnx_copy_override')
|
||||||
col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
|
col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
|
||||||
|
|
||||||
@ -1517,7 +1535,7 @@ class LNX_PT_TopbarPanel(bpy.types.Panel):
|
|||||||
bl_label = "Leenkx Player"
|
bl_label = "Leenkx Player"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "WINDOW"
|
bl_region_type = "WINDOW"
|
||||||
bl_options = {'INSTANCED'}
|
bl_options = get_panel_options()
|
||||||
|
|
||||||
def draw_header(self, context):
|
def draw_header(self, context):
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
@ -2921,7 +2939,7 @@ def draw_conditional_prop(layout: bpy.types.UILayout, heading: str, data: bpy.ty
|
|||||||
"""Draws a property row with a checkbox that enables a value field.
|
"""Draws a property row with a checkbox that enables a value field.
|
||||||
The function fails when prop_condition is not a boolean property.
|
The function fails when prop_condition is not a boolean property.
|
||||||
"""
|
"""
|
||||||
col = layout.column(heading=heading)
|
col = column_with_heading(layout, heading)
|
||||||
row = col.row()
|
row = col.row()
|
||||||
row.prop(data, prop_condition, text='')
|
row.prop(data, prop_condition, text='')
|
||||||
sub = row.row()
|
sub = row.row()
|
||||||
|
@ -96,7 +96,7 @@ def convert_image(image, path, file_format='JPEG'):
|
|||||||
ren.image_settings.color_mode = orig_color_mode
|
ren.image_settings.color_mode = orig_color_mode
|
||||||
|
|
||||||
|
|
||||||
def get_random_color_rgb() -> list[float]:
|
def get_random_color_rgb() -> List[float]:
|
||||||
"""Return a random RGB color with values in range [0, 1]."""
|
"""Return a random RGB color with values in range [0, 1]."""
|
||||||
return [random.random(), random.random(), random.random()]
|
return [random.random(), random.random(), random.random()]
|
||||||
|
|
||||||
@ -1162,7 +1162,7 @@ def get_link_web_server():
|
|||||||
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
|
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
|
||||||
|
|
||||||
|
|
||||||
def get_file_lnx_version_tuple() -> tuple[int]:
|
def get_file_lnx_version_tuple() -> Tuple[int, ...]:
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
return tuple(map(int, wrd.lnx_version.split('.')))
|
return tuple(map(int, wrd.lnx_version.split('.')))
|
||||||
|
|
||||||
@ -1218,9 +1218,9 @@ def cpu_count(*, physical_only=False) -> Optional[int]:
|
|||||||
return int(subprocess.check_output(command))
|
return int(subprocess.check_output(command))
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
err_reason = f'Reason: command {command} exited with code {e.returncode}.'
|
err_reason = 'Reason: command {} exited with code {}.'.format(command, e.returncode)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
err_reason = f'Reason: couldn\'t open file from command {command} ({e.errno=}).'
|
err_reason = 'Reason: couldn\'t open file from command {} (errno={}).'.format(command, e.errno)
|
||||||
|
|
||||||
# Last resort even though it can be wrong
|
# Last resort even though it can be wrong
|
||||||
log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason)
|
log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason)
|
||||||
|
@ -5,7 +5,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any, Optional, Callable
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ def is_version_installed(version_major: str) -> bool:
|
|||||||
return any(v['version_major'] == version_major for v in _installed_versions)
|
return any(v['version_major'] == version_major for v in _installed_versions)
|
||||||
|
|
||||||
|
|
||||||
def get_installed_version(version_major: str, re_fetch=False) -> Optional[dict[str, str]]:
|
def get_installed_version(version_major: str, re_fetch=False) -> Optional[Dict[str, str]]:
|
||||||
for installed_version in _installed_versions:
|
for installed_version in _installed_versions:
|
||||||
if installed_version['version_major'] == version_major:
|
if installed_version['version_major'] == version_major:
|
||||||
return installed_version
|
return installed_version
|
||||||
@ -71,7 +71,7 @@ def get_installed_version(version_major: str, re_fetch=False) -> Optional[dict[s
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_supported_version(version_major: str) -> Optional[dict[str, str]]:
|
def get_supported_version(version_major: str) -> Optional[Dict[str, str]]:
|
||||||
for version in supported_versions:
|
for version in supported_versions:
|
||||||
if version[0] == version_major:
|
if version[0] == version_major:
|
||||||
return {
|
return {
|
||||||
@ -100,7 +100,7 @@ def fetch_installed_vs(silent=False) -> bool:
|
|||||||
if not silent:
|
if not silent:
|
||||||
log.warn(
|
log.warn(
|
||||||
f'Found a Visual Studio installation with incomplete information, skipping\n'
|
f'Found a Visual Studio installation with incomplete information, skipping\n'
|
||||||
f' ({name=}, {versions=}, {path=})'
|
f' (name={name if name is not None else "None"}, versions={versions}, path={path if path is not None else "None"})'
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -212,14 +212,14 @@ def compile_in_vs(version_major: str, done: Callable[[], None]) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_display_name(instance_data: dict[str, Any]) -> Optional[str]:
|
def _vswhere_get_display_name(instance_data: Dict[str, Any]) -> Optional[str]:
|
||||||
name_raw = instance_data.get('displayName', None)
|
name_raw = instance_data.get('displayName', None)
|
||||||
if name_raw is None:
|
if name_raw is None:
|
||||||
return None
|
return None
|
||||||
return lnx.utils.safestr(name_raw).replace('_', ' ').strip()
|
return lnx.utils.safestr(name_raw).replace('_', ' ').strip()
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_version(instance_data: dict[str, Any]) -> Optional[tuple[str, str, tuple[int, ...]]]:
|
def _vswhere_get_version(instance_data: Dict[str, Any]) -> Optional[Tuple[str, str, Tuple[int, int, int, int]]]:
|
||||||
version_raw = instance_data.get('installationVersion', None)
|
version_raw = instance_data.get('installationVersion', None)
|
||||||
if version_raw is None:
|
if version_raw is None:
|
||||||
return None
|
return None
|
||||||
@ -230,11 +230,11 @@ def _vswhere_get_version(instance_data: dict[str, Any]) -> Optional[tuple[str, s
|
|||||||
return version_major, version_full, version_full_ints
|
return version_major, version_full, version_full_ints
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_path(instance_data: dict[str, Any]) -> Optional[str]:
|
def _vswhere_get_path(instance_data: Dict[str, Any]) -> Optional[str]:
|
||||||
return instance_data.get('installationPath', None)
|
return instance_data.get('installationPath', None)
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_instances(silent=False) -> Optional[list[dict[str, Any]]]:
|
def _vswhere_get_instances(silent: bool = False) -> Optional[List[Dict[str, Any]]]:
|
||||||
# vswhere.exe only exists at that location since VS2017 v15.2, for
|
# vswhere.exe only exists at that location since VS2017 v15.2, for
|
||||||
# earlier versions we'd need to package vswhere with Leenkx
|
# earlier versions we'd need to package vswhere with Leenkx
|
||||||
exe_path = os.path.join(os.environ["ProgramFiles(x86)"], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
|
exe_path = os.path.join(os.environ["ProgramFiles(x86)"], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
|
||||||
@ -256,7 +256,7 @@ def _vswhere_get_instances(silent=False) -> Optional[list[dict[str, Any]]]:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def version_full_to_ints(version_full: str) -> tuple[int, ...]:
|
def version_full_to_ints(version_full: str) -> Tuple[int, ...]:
|
||||||
return tuple(int(i) for i in version_full.split('.'))
|
return tuple(int(i) for i in version_full.split('.'))
|
||||||
|
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ def get_vcxproj_path() -> str:
|
|||||||
return os.path.join(project_path, project_name + '.vcxproj')
|
return os.path.join(project_path, project_name + '.vcxproj')
|
||||||
|
|
||||||
|
|
||||||
def fetch_project_version() -> tuple[Optional[str], Optional[str], Optional[str]]:
|
def fetch_project_version() -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
||||||
version_major = None
|
version_major = None
|
||||||
version_min_full = None
|
version_min_full = None
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user