From a926fa8dbba61670fd9f69ab05de4aa6cd0017d6 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Sat, 27 Sep 2025 03:03:08 +0000 Subject: [PATCH 1/4] Update leenkx/blender/lnx/nodes_logic.py --- leenkx/blender/lnx/nodes_logic.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/leenkx/blender/lnx/nodes_logic.py b/leenkx/blender/lnx/nodes_logic.py index a591702..de0c9c8 100644 --- a/leenkx/blender/lnx/nodes_logic.py +++ b/leenkx/blender/lnx/nodes_logic.py @@ -477,7 +477,6 @@ __REG_CLASSES = ( LnxOpenNodeWikiEntry, LNX_OT_ReplaceNodesOperator, LNX_OT_RecalculateRotations, - LNX_MT_NodeAddOverride, LNX_OT_AddNodeOverride, LNX_UL_InterfaceSockets, LNX_PT_LogicNodePanel, @@ -492,8 +491,9 @@ def register(): lnx.logicnode.lnx_node_group.register() lnx.logicnode.tree_variables.register() - LNX_MT_NodeAddOverride.overridden_menu = bpy.types.NODE_MT_add + # Store original draw method and restore during unregister LNX_MT_NodeAddOverride.overridden_draw = bpy.types.NODE_MT_add.draw + bpy.types.NODE_MT_add.draw = LNX_MT_NodeAddOverride.draw __reg_classes() @@ -508,8 +508,11 @@ def unregister(): # Ensure that globals are reset if the addon is enabled again in the same Blender session 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() - bpy.utils.register_class(LNX_MT_NodeAddOverride.overridden_menu) lnx.logicnode.tree_variables.unregister() lnx.logicnode.lnx_node_group.unregister() From 8f8d4b13767c1162eef6487644aab3360da4f5aa Mon Sep 17 00:00:00 2001 From: Onek8 Date: Sun, 28 Sep 2025 00:09:57 +0000 Subject: [PATCH 2/4] Update leenkx/blender/lnx/props_traits.py --- leenkx/blender/lnx/props_traits.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/leenkx/blender/lnx/props_traits.py b/leenkx/blender/lnx/props_traits.py index 5db9309..48fafe2 100644 --- a/leenkx/blender/lnx/props_traits.py +++ b/leenkx/blender/lnx/props_traits.py @@ -475,12 +475,10 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator): # If not, append vertex traversed_indices.append(vertex_index) vertex = export_mesh.vertices[vertex_index].co - # Apply world transform + # Apply world transform and maintain coordinate system tv = world_matrix @ vertex - # Write to OBJ - f.write("v %.4f " % (tv[0])) - f.write("%.4f " % (tv[2])) - f.write("%.4f\n" % (tv[1])) # Flipped + # Write to OBJ without flipping coordinates + f.write("v %.4f %.4f %.4f\n" % (tv[0], tv[1], tv[2])) # Max index of this object max_index = 0 @@ -524,8 +522,10 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator): # NavMesh preview settings, cleanup navmesh.name = nav_mesh_name - navmesh.rotation_euler = (0, 0, 0) - navmesh.location = (0, 0, 0) + # Match the original object's transform + navmesh.location = obj.location + navmesh.rotation_euler = obj.rotation_euler + navmesh.scale = (1, 1, 1) # Reset scale to avoid distortion navmesh.lnx_export = False bpy.context.view_layer.objects.active = navmesh From f97d8fd84673b5b92b2de9190f6e817865cea57f Mon Sep 17 00:00:00 2001 From: Onek8 Date: Sun, 28 Sep 2025 12:44:04 -0700 Subject: [PATCH 3/4] Blender 2.8 - 4.5 Support --- leenkx.py | 58 +++- leenkx/blender/data/lnx_data_2.blend | Bin 0 -> 100308 bytes leenkx/blender/lnx/__init__.py | 10 +- leenkx/blender/lnx/exporter.py | 71 +++-- leenkx/blender/lnx/exporter_opt.py | 10 +- leenkx/blender/lnx/handlers.py | 25 +- leenkx/blender/lnx/lib/make_datas.py | 35 ++- .../blender/lnx/lightmapper/operators/tlm.py | 21 +- .../blender/lnx/lightmapper/panels/image.py | 7 +- .../logicnode/animation/LN_blend_space_gpu.py | 4 +- .../lnx/logicnode/custom/LN_create_element.py | 286 +++++++++--------- .../logicnode/custom/LN_js_event_target.py | 23 +- .../lnx/logicnode/custom/LN_render_element.py | 41 ++- .../blender/lnx/logicnode/lnx_node_group.py | 5 +- leenkx/blender/lnx/logicnode/lnx_nodes.py | 14 +- leenkx/blender/lnx/logicnode/lnx_props.py | 14 +- .../logicnode/miscellaneous/LN_group_input.py | 5 +- .../miscellaneous/LN_group_output.py | 5 +- .../blender/lnx/logicnode/tree_variables.py | 5 +- leenkx/blender/lnx/make_logic.py | 14 +- .../lnx/material/cycles_nodes/nodes_shader.py | 16 +- leenkx/blender/lnx/material/make_mesh.py | 6 +- leenkx/blender/lnx/material/make_particle.py | 89 +++--- leenkx/blender/lnx/material/mat_utils.py | 4 +- leenkx/blender/lnx/material/node_meta.py | 4 +- leenkx/blender/lnx/node_utils.py | 8 +- leenkx/blender/lnx/nodes_logic.py | 9 +- leenkx/blender/lnx/props.py | 22 +- leenkx/blender/lnx/props_exporter.py | 9 +- leenkx/blender/lnx/props_traits.py | 59 ++-- leenkx/blender/lnx/props_traits_props.py | 37 ++- leenkx/blender/lnx/props_ui.py | 36 ++- leenkx/blender/lnx/utils.py | 8 +- leenkx/blender/lnx/utils_vs.py | 20 +- 34 files changed, 581 insertions(+), 399 deletions(-) create mode 100644 leenkx/blender/data/lnx_data_2.blend diff --git a/leenkx.py b/leenkx.py index 83fe3fa..ed07a18 100644 --- a/leenkx.py +++ b/leenkx.py @@ -24,7 +24,7 @@ import textwrap import threading import traceback import typing -from typing import Callable, Optional +from typing import Callable, Optional, List import webbrowser import bpy @@ -33,6 +33,12 @@ from bpy.props import * from bpy.types import Operator, AddonPreferences +if bpy.app.version < (2, 90, 0): + ListType = List +else: + ListType = list + + class SDKSource(IntEnum): PREFS = 0 LOCAL = 1 @@ -58,8 +64,45 @@ def get_os(): else: return 'linux' - 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.""" # Do not overwrite the SDK path (this method gets # called after each registration, not after @@ -73,6 +116,7 @@ def detect_sdk_path(): 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() @@ -558,7 +602,7 @@ def remove_readonly(func, path, excinfo): 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): p.wait() if done is not None: @@ -840,7 +884,13 @@ def update_leenkx_py(sdk_path: str, force_relink=False): else: raise err else: - lnx_module_file.unlink(missing_ok=True) + if bpy.app.version < (2, 92, 0): + try: + lnx_module_file.unlink() + except FileNotFoundError: + pass + else: + lnx_module_file.unlink(missing_ok=True) shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file) diff --git a/leenkx/blender/data/lnx_data_2.blend b/leenkx/blender/data/lnx_data_2.blend new file mode 100644 index 0000000000000000000000000000000000000000..c444eb0122fd12b7ab8a652323843856cc88c145 GIT binary patch literal 100308 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�E_2=uk8{-b)-Gr}~)OS@nA*oWS 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 Vq72&YeAhE7*o= (3, 0, 0): + VertexColorType = bpy.types.Attribute +else: + VertexColorType = bpy.types.MeshLoopColorLayer import numpy as np @@ -138,7 +145,7 @@ class LeenkxExporter: self.world_array = [] self.particle_system_array = {} - self.referenced_collections: list[bpy.types.Collection] = [] + self.referenced_collections: List[bpy.types.Collection] = [] """Collections referenced by collection instances""" self.has_spawning_camera = False @@ -1449,31 +1456,38 @@ class LeenkxExporter: @staticmethod def get_num_vertex_colors(mesh: bpy.types.Mesh) -> int: """Return the amount of vertex color attributes of the given mesh.""" - num = 0 - for attr in mesh.attributes: - if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'): - if attr.domain == 'CORNER': - num += 1 - else: - log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"') - - return num + if bpy.app.version >= (3, 0, 0): + num = 0 + for attr in mesh.attributes: + if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'): + if attr.domain == 'CORNER': + num += 1 + else: + log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"') + return num + else: + return len(mesh.vertex_colors) @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, ignoring all other attribute types and unsupported domains. """ - i = 0 - for attr in mesh.attributes: - if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'): - if attr.domain != 'CORNER': - log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"') - continue - if i == n: - return attr - i += 1 - return None + if bpy.app.version >= (3, 0, 0): + i = 0 + for attr in mesh.attributes: + if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'): + if attr.domain != 'CORNER': + log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"') + continue + if i == n: + return attr + i += 1 + return None + else: + if 0 <= n < len(mesh.vertex_colors): + return mesh.vertex_colors[n] + return None @staticmethod 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 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': debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0 diff --git a/leenkx/blender/lnx/exporter_opt.py b/leenkx/blender/lnx/exporter_opt.py index eff669f..91386e3 100644 --- a/leenkx/blender/lnx/exporter_opt.py +++ b/leenkx/blender/lnx/exporter_opt.py @@ -2,8 +2,7 @@ Exports smaller geometry but is slower. To be replaced with https://github.com/zeux/meshoptimizer """ -from typing import Optional - +from typing import Optional, TYPE_CHECKING import bpy from mathutils import Vector import numpy as np @@ -21,7 +20,12 @@ else: class Vertex: __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 loop_idx = loop.index self.co = mesh.vertices[self.vertex_index].co[:] diff --git a/leenkx/blender/lnx/handlers.py b/leenkx/blender/lnx/handlers.py index 5d62a25..b29ecd1 100644 --- a/leenkx/blender/lnx/handlers.py +++ b/leenkx/blender/lnx/handlers.py @@ -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 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" # but follow the bl_label attribute by default. New logic trees # are thus called "Leenkx Logic Editor" which conflicts with Haxe's @@ -132,9 +132,10 @@ def send_operator(op): def always() -> float: # Force ui redraw if state.redraw_ui: - for area in bpy.context.screen.areas: - if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'): - area.tag_redraw() + if bpy.context.screen is not None: + for area in bpy.context.screen.areas: + if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'): + area.tag_redraw() state.redraw_ui = False return 0.5 @@ -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 @@ -347,10 +348,18 @@ def reload_blend_data(): def load_library(asset_name): - if bpy.data.filepath.endswith('lnx_data.blend'): # Prevent load in library itself - return + # 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 sdk_path = lnx.utils.get_sdk_path() - data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend' + 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_names = [asset_name] # Import diff --git a/leenkx/blender/lnx/lib/make_datas.py b/leenkx/blender/lnx/lib/make_datas.py index 6483445..a47a839 100644 --- a/leenkx/blender/lnx/lib/make_datas.py +++ b/leenkx/blender/lnx/lib/make_datas.py @@ -1,13 +1,15 @@ +from typing import List, Dict, Optional, Any + import lnx.utils from lnx import assets def parse_context( - c: dict, - sres: dict, - asset, - defs: list[str], - vert: list[str] = None, - frag: list[str] = None, + c: Dict[str, Any], + sres: Dict[str, Any], + asset: Any, + defs: List[str], + vert: Optional[List[str]] = None, + frag: Optional[List[str]] = None, ): con = { "name": c["name"], @@ -99,7 +101,12 @@ def parse_context( 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 elements, uniforms and constants. This information is later used in @@ -229,7 +236,12 @@ def parse_shader( 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`) 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, @@ -273,7 +285,12 @@ def check_link(source_context: dict, defs: list[str], cid: str, out: dict): 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": []} res["shader_datas"].append(sres) diff --git a/leenkx/blender/lnx/lightmapper/operators/tlm.py b/leenkx/blender/lnx/lightmapper/operators/tlm.py index 01046df..46bfda6 100644 --- a/leenkx/blender/lnx/lightmapper/operators/tlm.py +++ b/leenkx/blender/lnx/lightmapper/operators/tlm.py @@ -1049,17 +1049,18 @@ class TLM_ToggleTexelDensity(bpy.types.Operator): #img = bpy.data.images.load(filepath) - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - space_data = area.spaces.active - bpy.ops.screen.area_dupli('INVOKE_DEFAULT') - new_window = context.window_manager.windows[-1] + if bpy.context.screen is not None: + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + space_data = area.spaces.active + bpy.ops.screen.area_dupli('INVOKE_DEFAULT') + new_window = context.window_manager.windows[-1] - area = new_window.screen.areas[-1] - area.type = 'VIEW_3D' - #bg = space_data.background_images.new() - print(bpy.context.object) - bpy.ops.object.bake_td_uv_to_vc() + area = new_window.screen.areas[-1] + area.type = 'VIEW_3D' + #bg = space_data.background_images.new() + print(bpy.context.object) + bpy.ops.object.bake_td_uv_to_vc() #bg.image = img break diff --git a/leenkx/blender/lnx/lightmapper/panels/image.py b/leenkx/blender/lnx/lightmapper/panels/image.py index 929907e..425ea82 100644 --- a/leenkx/blender/lnx/lightmapper/panels/image.py +++ b/leenkx/blender/lnx/lightmapper/panels/image.py @@ -28,9 +28,10 @@ class TLM_PT_Imagetools(bpy.types.Panel): activeImg = None - for area in bpy.context.screen.areas: - if area.type == 'IMAGE_EDITOR': - activeImg = area.spaces.active.image + if bpy.context.screen is not None: + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + activeImg = area.spaces.active.image if activeImg is not None and activeImg.name != "Render Result" and activeImg.name != "Viewer Node": diff --git a/leenkx/blender/lnx/logicnode/animation/LN_blend_space_gpu.py b/leenkx/blender/lnx/logicnode/animation/LN_blend_space_gpu.py index 24f636e..198c71a 100644 --- a/leenkx/blender/lnx/logicnode/animation/LN_blend_space_gpu.py +++ b/leenkx/blender/lnx/logicnode/animation/LN_blend_space_gpu.py @@ -103,11 +103,11 @@ class BlendSpaceNode(LnxLogicTreeNode): self.remove_advanced_draw() 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 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() def lnx_init(self, context): diff --git a/leenkx/blender/lnx/logicnode/custom/LN_create_element.py b/leenkx/blender/lnx/logicnode/custom/LN_create_element.py index 2436a89..9d9f410 100644 --- a/leenkx/blender/lnx/logicnode/custom/LN_create_element.py +++ b/leenkx/blender/lnx/logicnode/custom/LN_create_element.py @@ -156,149 +156,149 @@ class CreateElementNode(LnxLogicTreeNode): self.add_input('LnxStringSocket', 'Class') self.add_input('LnxStringSocket', 'Style') - match index: - case 0: - self.add_input('LnxStringSocket', 'Href', default_value='#') - case 3: - self.add_input('LnxStringSocket', 'Alt') - self.add_input('LnxStringSocket', 'Coords') - self.add_input('LnxStringSocket', 'Href') - case 6: - self.add_input('LnxStringSocket', 'Src') - case 11: - self.add_input('LnxStringSocket', 'Cite', default_value='URL') - case 14: - self.add_input('LnxStringSocket', 'Type', default_value='Submit') - case 15: - self.add_input('LnxStringSocket', 'Height', default_value='150px') - self.add_input('LnxStringSocket', 'Width', default_value='300px') - case 19 | 20: - self.add_input('LnxStringSocket', 'Span') - case 21: - self.add_input('LnxStringSocket', 'Value') - case 24 | 53: - self.add_input('LnxStringSocket', 'Cite', default_value='URL') - self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD') - case 26: - self.add_input('LnxStringSocket', 'Title') - case 32: - self.add_input('LnxStringSocket', 'Src', default_value='URL') - self.add_input('LnxStringSocket', 'Type') - self.add_input('LnxStringSocket', 'Height') - self.add_input('LnxStringSocket', 'Width') - case 33: - self.add_input('LnxStringSocket', 'Form') - self.add_input('LnxStringSocket', 'Name') - case 37: - self.add_input('LnxStringSocket', 'Action', default_value='URL') - self.add_input('LnxStringSocket', 'Method', default_value='get') - case 44: - self.add_input('LnxStringSocket', 'Profile', default_value='URI') - case 48: - self.add_input('LnxBoolSocket', 'xmlns' , default_value=False ) - case 50: - self.add_input('LnxStringSocket', 'Src', default_value='URL') - self.add_input('LnxStringSocket', 'Height' , default_value="150px" ) - self.add_input('LnxStringSocket', 'Width', default_value='300px') - case 51: - self.add_input('LnxStringSocket', 'Src') - self.add_input('LnxStringSocket', 'Height' , default_value='150px') - self.add_input('LnxStringSocket', 'Width', default_value='150px') - case 52: - self.add_input('LnxStringSocket', 'Type', default_value='text') - self.add_input('LnxStringSocket', 'Value') - case 55: - self.add_input('LnxStringSocket', 'For', default_value='element_id') - self.add_input('LnxStringSocket', 'Form', default_value='form_id') - case 57: - self.add_input('LnxStringSocket', 'Value') - case 58: - self.add_input('LnxStringSocket', 'Href', default_value='#') - self.add_input('LnxStringSocket', 'Hreflang', default_value='en') - self.add_input('LnxStringSocket', 'Title') - case 58: - self.add_input('LnxStringSocket', 'Name', default_value='mapname') - case 63: - self.add_input('LnxStringSocket', 'Charset', default_value='character_set') - self.add_input('LnxStringSocket', 'Content', default_value='text') - case 64: - self.add_input('LnxStringSocket', 'form', default_value='form_id') - self.add_input('LnxStringSocket', 'high') - self.add_input('LnxStringSocket', 'low') - self.add_input('LnxStringSocket', 'max') - self.add_input('LnxStringSocket', 'min') - self.add_input('LnxStringSocket', 'optimum') - self.add_input('LnxStringSocket', 'value') - case 67: - self.add_input('LnxStringSocket', 'data', default_value='URL') - self.add_input('LnxStringSocket', 'form', default_value='form_id') - self.add_input('LnxStringSocket', 'height', default_value='pixels') - self.add_input('LnxStringSocket', 'name', default_value='name') - self.add_input('LnxStringSocket', 'type', default_value='media_type') - self.add_input('LnxStringSocket', 'usemap', default_value='#mapname') - self.add_input('LnxStringSocket', 'width', default_value='pixels') - case 68: - self.add_input('LnxStringSocket', 'start', default_value='number') - case 69: - self.add_input('LnxStringSocket', 'label', default_value='text') - case 70: - self.add_input('LnxStringSocket', 'label', default_value='text') - self.add_input('LnxStringSocket', 'value', default_value='value') - case 71: - self.add_input('LnxStringSocket', 'for', default_value='element_id') - self.add_input('LnxStringSocket', 'form', default_value='form_id') - self.add_input('LnxStringSocket', 'name', default_value='name') - case 75: - self.add_input('LnxStringSocket', 'max', default_value='number') - self.add_input('LnxStringSocket', 'value', default_value='number') - case 76: - self.add_input('LnxStringSocket', 'cite', default_value='URL') - case 78: - self.add_input('LnxStringSocket', 'cite', default_value='URL') - case 79: - self.add_input('LnxStringSocket', 'integrity' , default_value='filehash') - self.add_input('LnxStringSocket', 'Src') - self.add_input('LnxStringSocket', 'type', default_value='scripttype') - case 81: - self.add_input('LnxStringSocket', 'form' , default_value='form_id') - self.add_input('LnxStringSocket', 'name' , default_value='text') - self.add_input('LnxStringSocket', 'type', default_value='scripttype') - self.add_input('LnxStringSocket', 'size', default_value='number') - case 84: - self.add_input('LnxStringSocket', 'size') - self.add_input('LnxStringSocket', 'src' , default_value='URL') - self.add_input('LnxStringSocket', 'srcset', default_value='URL') - case 87: - self.add_input('LnxStringSocket', 'type', default_value='media_type') - case 93: - self.add_input('LnxStringSocket', 'colspan' , default_value='number') - self.add_input('LnxStringSocket', 'headers' , default_value='header_id') - self.add_input('LnxStringSocket', 'rowspan', default_value='number') - case 95: - self.add_input('LnxStringSocket', 'cols' , default_value='number') - self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir') - self.add_input('LnxStringSocket', 'rowspan', default_value='number') - self.add_input('LnxStringSocket', 'form', default_value='form_id') - self.add_input('LnxStringSocket', 'maxlength', default_value='number') - self.add_input('LnxStringSocket', 'name' , default_value='text') - self.add_input('LnxStringSocket', 'placeholder' , default_value='text') - self.add_input('LnxStringSocket', 'rows' , default_value='number') - case 97: - self.add_input('LnxStringSocket', 'abbr' , default_value='text') - self.add_input('LnxStringSocket', 'colspan' , default_value='number') - self.add_input('LnxStringSocket', 'headers', default_value='header_id') - self.add_input('LnxStringSocket', 'rowspan', default_value='number') - case 99: - self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD') - case 102: - self.add_input('LnxStringSocket', 'Src', default_value='URL') - self.add_input('LnxStringSocket', 'srclang', default_value='en') - self.add_input('LnxStringSocket', 'label', default_value='text') - case 106: - self.add_input('LnxStringSocket', 'Src', default_value='URL') - self.add_input('LnxStringSocket', 'width', default_value='pixels') - self.add_input('LnxStringSocket', 'height', default_value='pixels') - self.add_input('LnxStringSocket', 'poster', default_value='URL') + if index == 0: + self.add_input('LnxStringSocket', 'Href', default_value='#') + elif index == 3: + self.add_input('LnxStringSocket', 'Alt') + self.add_input('LnxStringSocket', 'Coords') + self.add_input('LnxStringSocket', 'Href') + elif index == 6: + self.add_input('LnxStringSocket', 'Src') + elif index == 11: + self.add_input('LnxStringSocket', 'Cite', default_value='URL') + elif index == 14: + self.add_input('LnxStringSocket', 'Type', default_value='Submit') + elif index == 15: + self.add_input('LnxStringSocket', 'Height', default_value='150px') + self.add_input('LnxStringSocket', 'Width', default_value='300px') + elif index in (19, 20): + self.add_input('LnxStringSocket', 'Span') + elif index == 21: + self.add_input('LnxStringSocket', 'Value') + elif index in (24, 53): + self.add_input('LnxStringSocket', 'Cite', default_value='URL') + self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD') + elif index == 26: + self.add_input('LnxStringSocket', 'Title') + elif index == 32: + self.add_input('LnxStringSocket', 'Src', default_value='URL') + self.add_input('LnxStringSocket', 'Type') + self.add_input('LnxStringSocket', 'Height') + self.add_input('LnxStringSocket', 'Width') + elif index == 33: + self.add_input('LnxStringSocket', 'Form') + self.add_input('LnxStringSocket', 'Name') + elif index == 37: + self.add_input('LnxStringSocket', 'Action', default_value='URL') + self.add_input('LnxStringSocket', 'Method', default_value='get') + elif index == 44: + self.add_input('LnxStringSocket', 'Profile', default_value='URI') + elif index == 48: + self.add_input('LnxBoolSocket', 'xmlns' , default_value=False ) + elif index == 50: + self.add_input('LnxStringSocket', 'Src', default_value='URL') + self.add_input('LnxStringSocket', 'Height' , default_value="150px" ) + self.add_input('LnxStringSocket', 'Width', default_value='300px') + elif index == 51: + self.add_input('LnxStringSocket', 'Src') + self.add_input('LnxStringSocket', 'Height' , default_value='150px') + self.add_input('LnxStringSocket', 'Width', default_value='150px') + elif index == 52: + self.add_input('LnxStringSocket', 'Type', default_value='text') + self.add_input('LnxStringSocket', 'Value') + elif index == 55: + self.add_input('LnxStringSocket', 'For', default_value='element_id') + self.add_input('LnxStringSocket', 'Form', default_value='form_id') + elif index == 57: + self.add_input('LnxStringSocket', 'Value') + elif index == 58: + self.add_input('LnxStringSocket', 'Href', default_value='#') + self.add_input('LnxStringSocket', 'Hreflang', default_value='en') + self.add_input('LnxStringSocket', 'Title') + # 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') + elif index == 63: + self.add_input('LnxStringSocket', 'Charset', default_value='character_set') + self.add_input('LnxStringSocket', 'Content', default_value='text') + elif index == 64: + self.add_input('LnxStringSocket', 'form', default_value='form_id') + self.add_input('LnxStringSocket', 'high') + self.add_input('LnxStringSocket', 'low') + self.add_input('LnxStringSocket', 'max') + self.add_input('LnxStringSocket', 'min') + self.add_input('LnxStringSocket', 'optimum') + self.add_input('LnxStringSocket', 'value') + elif index == 67: + self.add_input('LnxStringSocket', 'data', default_value='URL') + self.add_input('LnxStringSocket', 'form', default_value='form_id') + self.add_input('LnxStringSocket', 'height', default_value='pixels') + self.add_input('LnxStringSocket', 'name', default_value='name') + self.add_input('LnxStringSocket', 'type', default_value='media_type') + self.add_input('LnxStringSocket', 'usemap', default_value='#mapname') + self.add_input('LnxStringSocket', 'width', default_value='pixels') + elif index == 68: + self.add_input('LnxStringSocket', 'start', default_value='number') + elif index == 69: + self.add_input('LnxStringSocket', 'label', default_value='text') + elif index == 70: + self.add_input('LnxStringSocket', 'label', default_value='text') + self.add_input('LnxStringSocket', 'value', default_value='value') + elif index == 71: + self.add_input('LnxStringSocket', 'for', default_value='element_id') + self.add_input('LnxStringSocket', 'form', default_value='form_id') + self.add_input('LnxStringSocket', 'name', default_value='name') + elif index == 75: + self.add_input('LnxStringSocket', 'max', default_value='number') + self.add_input('LnxStringSocket', 'value', default_value='number') + elif index == 76: + self.add_input('LnxStringSocket', 'cite', default_value='URL') + elif index == 78: + self.add_input('LnxStringSocket', 'cite', default_value='URL') + elif index == 79: + self.add_input('LnxStringSocket', 'integrity' , default_value='filehash') + self.add_input('LnxStringSocket', 'Src') + self.add_input('LnxStringSocket', 'type', default_value='scripttype') + elif index == 81: + self.add_input('LnxStringSocket', 'form' , default_value='form_id') + self.add_input('LnxStringSocket', 'name' , default_value='text') + self.add_input('LnxStringSocket', 'type', default_value='scripttype') + self.add_input('LnxStringSocket', 'size', default_value='number') + elif index == 84: + self.add_input('LnxStringSocket', 'size') + self.add_input('LnxStringSocket', 'src' , default_value='URL') + self.add_input('LnxStringSocket', 'srcset', default_value='URL') + elif index == 87: + self.add_input('LnxStringSocket', 'type', default_value='media_type') + elif index == 93: + self.add_input('LnxStringSocket', 'colspan' , default_value='number') + self.add_input('LnxStringSocket', 'headers' , default_value='header_id') + self.add_input('LnxStringSocket', 'rowspan', default_value='number') + elif index == 95: + self.add_input('LnxStringSocket', 'cols' , default_value='number') + self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir') + self.add_input('LnxStringSocket', 'rowspan', default_value='number') + self.add_input('LnxStringSocket', 'form', default_value='form_id') + self.add_input('LnxStringSocket', 'maxlength', default_value='number') + self.add_input('LnxStringSocket', 'name' , default_value='text') + self.add_input('LnxStringSocket', 'placeholder' , default_value='text') + self.add_input('LnxStringSocket', 'rows' , default_value='number') + elif index == 97: + self.add_input('LnxStringSocket', 'abbr' , default_value='text') + self.add_input('LnxStringSocket', 'colspan' , default_value='number') + self.add_input('LnxStringSocket', 'headers', default_value='header_id') + self.add_input('LnxStringSocket', 'rowspan', default_value='number') + elif index == 99: + self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD') + elif index == 102: + self.add_input('LnxStringSocket', 'Src', default_value='URL') + self.add_input('LnxStringSocket', 'srclang', default_value='en') + self.add_input('LnxStringSocket', 'label', default_value='text') + elif index == 106: + self.add_input('LnxStringSocket', 'Src', default_value='URL') + self.add_input('LnxStringSocket', 'width', default_value='pixels') + self.add_input('LnxStringSocket', 'height', default_value='pixels') + self.add_input('LnxStringSocket', 'poster', default_value='URL') for i in range(len(self.inputs)): if self.inputs[i].name in self.data_map: diff --git a/leenkx/blender/lnx/logicnode/custom/LN_js_event_target.py b/leenkx/blender/lnx/logicnode/custom/LN_js_event_target.py index 1ee7c28..cf4190d 100644 --- a/leenkx/blender/lnx/logicnode/custom/LN_js_event_target.py +++ b/leenkx/blender/lnx/logicnode/custom/LN_js_event_target.py @@ -38,18 +38,17 @@ class JSEventTargetNode(LnxLogicTreeNode): # Arguements for type Client index = self.get_count_in(select_current) - match index: - case 2: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'JS Object') - self.add_input('LnxDynamicSocket', 'Event') - case _: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'JS Object') - self.add_input('LnxStringSocket', 'Type') - self.add_input('LnxDynamicSocket', 'Listener') - self.add_input('LnxDynamicSocket', 'Options') - self.add_input('LnxBoolSocket', 'unTrusted') + if index == 2: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'JS Object') + self.add_input('LnxDynamicSocket', 'Event') + else: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'JS Object') + self.add_input('LnxStringSocket', 'Type') + self.add_input('LnxDynamicSocket', 'Listener') + self.add_input('LnxDynamicSocket', 'Options') + self.add_input('LnxBoolSocket', 'unTrusted') self['property0'] = value diff --git a/leenkx/blender/lnx/logicnode/custom/LN_render_element.py b/leenkx/blender/lnx/logicnode/custom/LN_render_element.py index 881b3b9..976b0de 100644 --- a/leenkx/blender/lnx/logicnode/custom/LN_render_element.py +++ b/leenkx/blender/lnx/logicnode/custom/LN_render_element.py @@ -43,27 +43,26 @@ class RenderElementNode(LnxLogicTreeNode): # Arguements for type Client index = self.get_count_in(select_current) - match index: - case 2: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'Torrent') - self.add_input('LnxStringSocket', 'Selector') - case 5: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'Element') - self.add_input('LnxStringSocket', 'HTML') - case 6: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'Element') - self.add_input('LnxStringSocket', 'Text') - case 7: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxStringSocket', 'HTML') - self.add_input('LnxStringSocket', 'Selector') - case _: - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxDynamicSocket', 'Element') - self.add_input('LnxStringSocket', 'Selector') + if index == 2: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'Torrent') + self.add_input('LnxStringSocket', 'Selector') + elif index == 5: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'Element') + self.add_input('LnxStringSocket', 'HTML') + elif index == 6: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'Element') + self.add_input('LnxStringSocket', 'Text') + elif index == 7: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxStringSocket', 'HTML') + self.add_input('LnxStringSocket', 'Selector') + else: + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxDynamicSocket', 'Element') + self.add_input('LnxStringSocket', 'Selector') self['property0'] = value diff --git a/leenkx/blender/lnx/logicnode/lnx_node_group.py b/leenkx/blender/lnx/logicnode/lnx_node_group.py index 261e1a5..388e50c 100644 --- a/leenkx/blender/lnx/logicnode/lnx_node_group.py +++ b/leenkx/blender/lnx/logicnode/lnx_node_group.py @@ -66,7 +66,10 @@ class LnxGroupTree(bpy.types.NodeTree): """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 tested_tree_upstream_trees = {t.name for t in self.upstream_trees()} - current_tree_downstream_trees = {p.node_tree.name for p in bpy.context.space_data.path} + if bpy.context.space_data is not None: + 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 return not shared_trees diff --git a/leenkx/blender/lnx/logicnode/lnx_nodes.py b/leenkx/blender/lnx/logicnode/lnx_nodes.py index 056c157..5133d95 100644 --- a/leenkx/blender/lnx/logicnode/lnx_nodes.py +++ b/leenkx/blender/lnx/logicnode/lnx_nodes.py @@ -2,9 +2,17 @@ from collections import OrderedDict import itertools import math 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 +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 from bpy.props import * from nodeitems_utils import NodeItem @@ -39,11 +47,11 @@ PKG_AS_CATEGORY = "__pkgcat__" nodes = [] category_items: ODict[str, List['LnxNodeCategory']] = OrderedDict() -array_nodes: dict[str, 'LnxLogicTreeNode'] = dict() +array_nodes: Dict[str, 'LnxLogicTreeNode'] = dict() # See LnxLogicTreeNode.update() # 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): diff --git a/leenkx/blender/lnx/logicnode/lnx_props.py b/leenkx/blender/lnx/logicnode/lnx_props.py index 020c4a7..f4c8b63 100644 --- a/leenkx/blender/lnx/logicnode/lnx_props.py +++ b/leenkx/blender/lnx/logicnode/lnx_props.py @@ -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 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 bpy @@ -49,6 +49,10 @@ def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any: # bpy.types.Bone, remove them here to prevent registration errors if 'tags' in kwargs: 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) @@ -87,7 +91,7 @@ def HaxeBoolVectorProperty( update=None, get=None, set=None -) -> list['bpy.types.BoolProperty']: +) -> List['bpy.types.BoolProperty']: """Declares a new BoolVectorProperty that has a Haxe counterpart with the given prop_name (Python and Haxe names must be identical for now). @@ -118,7 +122,7 @@ def HaxeEnumProperty( items: Sequence, name: str = "", description: str = "", - default: Union[str, set[str]] = None, + default: Union[str, Set[str]] = None, options: set = {'ANIMATABLE'}, override: set = set(), tags: set = set(), @@ -180,7 +184,7 @@ def HaxeFloatVectorProperty( update=None, get=None, set=None -) -> list['bpy.types.FloatProperty']: +) -> List['bpy.types.FloatProperty']: """Declares a new FloatVectorProperty that has a Haxe counterpart with the given prop_name (Python and Haxe names must be identical for now). @@ -232,7 +236,7 @@ def HaxeIntVectorProperty( update=None, get=None, set=None -) -> list['bpy.types.IntProperty']: +) -> List['bpy.types.IntProperty']: """Declares a new IntVectorProperty that has a Haxe counterpart with the given prop_name (Python and Haxe names must be identical for now). """ diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_input.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_input.py index c3cbdd8..ec699bb 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_input.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_input.py @@ -27,7 +27,10 @@ class GroupInputsNode(LnxLogicTreeNode): copy_override: BoolProperty(name='copy override', description='', default=False) def init(self, context): - tree = bpy.context.space_data.edit_tree + if bpy.context.space_data is not None: + tree = bpy.context.space_data.edit_tree + else: + return node_count = 0 for node in tree.nodes: if node.bl_idname == 'LNGroupInputsNode': diff --git a/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_output.py b/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_output.py index 8950f84..fb8372f 100644 --- a/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_output.py +++ b/leenkx/blender/lnx/logicnode/miscellaneous/LN_group_output.py @@ -27,7 +27,10 @@ class GroupOutputsNode(LnxLogicTreeNode): copy_override: BoolProperty(name='copy override', description='', default=False) def init(self, context): - tree = bpy.context.space_data.edit_tree + if bpy.context.space_data is not None: + tree = bpy.context.space_data.edit_tree + else: + return node_count = 0 for node in tree.nodes: if node.bl_idname == 'LNGroupOutputsNode': diff --git a/leenkx/blender/lnx/logicnode/tree_variables.py b/leenkx/blender/lnx/logicnode/tree_variables.py index d435634..3d3e15e 100644 --- a/leenkx/blender/lnx/logicnode/tree_variables.py +++ b/leenkx/blender/lnx/logicnode/tree_variables.py @@ -350,7 +350,10 @@ class LNX_PG_TreeVarListItem(bpy.types.PropertyGroup): def _set_name(self, value: str): old_name = self._get_name() - tree = bpy.context.space_data.path[-1].node_tree + if bpy.context.space_data is not None: + tree = bpy.context.space_data.path[-1].node_tree + else: + return # No valid context lst = tree.lnx_treevariableslist if value == '': diff --git a/leenkx/blender/lnx/make_logic.py b/leenkx/blender/lnx/make_logic.py index 58f6d53..da3ed78 100644 --- a/leenkx/blender/lnx/make_logic.py +++ b/leenkx/blender/lnx/make_logic.py @@ -1,5 +1,5 @@ import os -from typing import Optional, TextIO +from typing import List, Optional, TextIO, Dict, Any, TypeVar, TYPE_CHECKING import bpy @@ -17,14 +17,14 @@ if lnx.is_reload(__name__): else: lnx.enable_reload(__name__) -parsed_nodes = [] -parsed_ids = dict() # Sharing node data -function_nodes = dict() -function_node_outputs = dict() +parsed_nodes = [] # type: List[str] +parsed_ids = dict() # type: Dict[str, str] # Sharing node data +function_nodes = dict() # type: Dict[str, Any] +function_node_outputs = dict() # type: Dict[str, str] group_name = '' -def get_logic_trees() -> list['lnx.nodes_logic.LnxLogicTree']: +def get_logic_trees() -> List['lnx.nodes_logic.LnxLogicTree']: ar = [] for node_group in bpy.data.node_groups: 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 -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.""" global parsed_nodes global parsed_ids diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py index 4885430..970028e 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_shader.py @@ -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) -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: if state.parse_surface: 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_specular = c.parse_value_input(node.inputs[5]) 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)\ - 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)): + 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): emission_col = c.parse_vector_input(node.inputs[17]) - emission_strength = c.parse_value_input(node.inputs[18]) - state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) + state.out_emission_col = emission_col mat_state.emission_type = mat_state.EmissionType.SHADED else: mat_state.emission_type = mat_state.EmissionType.NO_EMISSION if state.parse_opacity: state.out_ior = c.parse_value_input(node.inputs[14]) - state.out_opacity = c.parse_value_input(node.inputs[19]) -if bpy.app.version >= (3, 0, 0) and bpy.app.version <= (4, 1, 0): + # In Blender 2.83, Alpha socket is at index 18, not 19 + 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: if state.parse_surface: c.write_normal(node.inputs[22]) diff --git a/leenkx/blender/lnx/material/make_mesh.py b/leenkx/blender/lnx/material/make_mesh.py index 41a756f..c30ca74 100644 --- a/leenkx/blender/lnx/material/make_mesh.py +++ b/leenkx/blender/lnx/material/make_mesh.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Optional +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union import bpy @@ -32,8 +32,8 @@ else: is_displacement = False # User callbacks -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: Optional[Callable[[Dict[str, Any], shader.Shader], bool]] = None +write_material_attribs_post: Optional[Callable[[Dict[str, Any], shader.Shader], None]] = None write_vertex_attribs: Optional[Callable[[shader.Shader], bool]] = None diff --git a/leenkx/blender/lnx/material/make_particle.py b/leenkx/blender/lnx/material/make_particle.py index f24a1ea..4e85a97 100644 --- a/leenkx/blender/lnx/material/make_particle.py +++ b/leenkx/blender/lnx/material/make_particle.py @@ -169,58 +169,57 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('float s = sin(p_angle);') vert.write('vec3 center = spos.xyz - p_location;') - match rotation_mode: - case 'OB_X': - 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('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;') + if rotation_mode == 'OB_X': + 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('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;') - if (not shadowmap): - 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('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));') - case 'OB_Y': - 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;') + if (not shadowmap): + 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('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.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('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;') - if (not shadowmap): - vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));') - case 'OB_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('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);') - vert.write('spos.xyz = vec3(rotation.x, rotation.y, ry.z) + p_location;') + if (not shadowmap): + vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));') + elif rotation_mode == 'OB_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('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);') + vert.write('spos.xyz = vec3(rotation.x, rotation.y, ry.z) + p_location;') - if (not shadowmap): - vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);') - 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('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));') - case 'VEL': - vert.write('vec3 forward = -normalize(p_velocity);') - vert.write('if (length(forward) > 1e-5) {') - vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);') + if (not shadowmap): + vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);') + 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('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));') + elif rotation_mode == 'VEL': + vert.write('vec3 forward = -normalize(p_velocity);') + vert.write('if (length(forward) > 1e-5) {') + vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);') - vert.write('if (abs(dot(forward, world_up)) > 0.999) {') - vert.write('world_up = vec3(-1.0, 0.0, 0.0);') - vert.write('}') + vert.write('if (abs(dot(forward, world_up)) > 0.999) {') + vert.write('world_up = vec3(-1.0, 0.0, 0.0);') + vert.write('}') - vert.write('vec3 right = cross(world_up, forward);') - vert.write('if (length(right) < 1e-5) {') - vert.write('forward = -forward;') - vert.write('right = cross(world_up, forward);') - vert.write('}') - vert.write('right = normalize(right);') - vert.write('vec3 up = normalize(cross(forward, right));') + vert.write('vec3 right = cross(world_up, forward);') + vert.write('if (length(right) < 1e-5) {') + vert.write('forward = -forward;') + vert.write('right = cross(world_up, forward);') + vert.write('}') + vert.write('right = normalize(right);') + vert.write('vec3 up = normalize(cross(forward, right));') - vert.write('mat3 rot = mat3(right, -forward, up);') - vert.write('mat3 phase = mat3(vec3(c, 0.0, -s), vec3(0.0, 1.0, 0.0), vec3(s, 0.0, c));') - vert.write('mat3 final_rot = rot * phase;') - vert.write('spos.xyz = final_rot * center + p_location;') + vert.write('mat3 rot = mat3(right, -forward, up);') + vert.write('mat3 phase = mat3(vec3(c, 0.0, -s), vec3(0.0, 1.0, 0.0), vec3(s, 0.0, c));') + vert.write('mat3 final_rot = rot * phase;') + vert.write('spos.xyz = final_rot * center + p_location;') - if (not shadowmap): - vert.write('wnormal = normalize(final_rot * wnormal);') - vert.write('}') + if (not shadowmap): + vert.write('wnormal = normalize(final_rot * wnormal);') + vert.write('}') if rotation_factor_random != 0: str_rotate_around = '''vec3 rotate_around(vec3 v, vec3 angle) { diff --git a/leenkx/blender/lnx/material/mat_utils.py b/leenkx/blender/lnx/material/mat_utils.py index 964bd8a..725dd72 100644 --- a/leenkx/blender/lnx/material/mat_utils.py +++ b/leenkx/blender/lnx/material/mat_utils.py @@ -1,4 +1,4 @@ -from typing import Generator +from typing import Generator, Tuple import bpy @@ -101,7 +101,7 @@ def iter_nodes_leenkxpbr(node_group: bpy.types.NodeTree) -> Generator[bpy.types. 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 # support direct comparison return ( diff --git a/leenkx/blender/lnx/material/node_meta.py b/leenkx/blender/lnx/material/node_meta.py index 823af79..4c3963a 100644 --- a/leenkx/blender/lnx/material/node_meta.py +++ b/leenkx/blender/lnx/material/node_meta.py @@ -4,7 +4,7 @@ This module contains a list of all material nodes that Leenkx supports """ from enum import IntEnum, unique from dataclasses import dataclass -from typing import Any, Callable, Optional +from typing import Any, Callable, Optional, Dict, List, Tuple, TypeVar, Union import bpy @@ -62,7 +62,7 @@ class MaterialNodeMeta: """ -ALL_NODES: dict[str, MaterialNodeMeta] = { +ALL_NODES: Dict[str, MaterialNodeMeta] = { # --- nodes_color 'BRIGHTCONTRAST': MaterialNodeMeta(parse_func=nodes_color.parse_brightcontrast), 'CURVE_RGB': MaterialNodeMeta(parse_func=nodes_color.parse_curvergb), diff --git a/leenkx/blender/lnx/node_utils.py b/leenkx/blender/lnx/node_utils.py index 97b4149..d7a4585 100644 --- a/leenkx/blender/lnx/node_utils.py +++ b/leenkx/blender/lnx/node_utils.py @@ -1,5 +1,5 @@ import collections.abc -from typing import Any, Generator, Optional, Type, Union +from typing import Any, Generator, Optional, Type, Tuple, Union import bpy import mathutils @@ -49,7 +49,7 @@ def iter_nodes_by_type(node_group: bpy.types.NodeTree, ntype: str) -> Generator[ 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 to the given input, while following reroutes. If the input has 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 -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 to the given output, while following reroutes. If the output has 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) -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 a counterpart in the node's Haxe class. """ diff --git a/leenkx/blender/lnx/nodes_logic.py b/leenkx/blender/lnx/nodes_logic.py index de0c9c8..a591702 100644 --- a/leenkx/blender/lnx/nodes_logic.py +++ b/leenkx/blender/lnx/nodes_logic.py @@ -477,6 +477,7 @@ __REG_CLASSES = ( LnxOpenNodeWikiEntry, LNX_OT_ReplaceNodesOperator, LNX_OT_RecalculateRotations, + LNX_MT_NodeAddOverride, LNX_OT_AddNodeOverride, LNX_UL_InterfaceSockets, LNX_PT_LogicNodePanel, @@ -491,9 +492,8 @@ def register(): lnx.logicnode.lnx_node_group.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 - bpy.types.NODE_MT_add.draw = LNX_MT_NodeAddOverride.draw __reg_classes() @@ -508,11 +508,8 @@ def unregister(): # Ensure that globals are reset if the addon is enabled again in the same Blender session 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() + bpy.utils.register_class(LNX_MT_NodeAddOverride.overridden_menu) lnx.logicnode.tree_variables.unregister() lnx.logicnode.lnx_node_group.unregister() diff --git a/leenkx/blender/lnx/props.py b/leenkx/blender/lnx/props.py index aa3949f..d981ea9 100644 --- a/leenkx/blender/lnx/props.py +++ b/leenkx/blender/lnx/props.py @@ -1,5 +1,13 @@ import bpy 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 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_winminimize = BoolProperty(name="Minimizable", description="Allow window minimize", default=True, update=assets.invalidate_compiler_cache) # For object - bpy.types.Object.lnx_instanced = EnumProperty( + bpy.types.Object.lnx_instanced = compatible_prop(EnumProperty, items = [('Off', 'Off', 'No instancing of children'), ('Loc', 'Loc', 'Instances use their unique position (ipos)'), ('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', update=assets.invalidate_instance_cache, override={'LIBRARY_OVERRIDABLE'}) - bpy.types.Object.lnx_export = 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_spawn = 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_visible = 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_export = compatible_prop(BoolProperty, name="Export", description="Export object data", default=True, 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 = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, 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 = compatible_prop(BoolProperty, name="Visible", description="Render this object", 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_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]) diff --git a/leenkx/blender/lnx/props_exporter.py b/leenkx/blender/lnx/props_exporter.py index e0c7766..ccb2110 100644 --- a/leenkx/blender/lnx/props_exporter.py +++ b/leenkx/blender/lnx/props_exporter.py @@ -420,16 +420,19 @@ class LNX_OT_ExporterOpenVS(bpy.types.Operator): @classmethod def poll(cls, context): if not lnx.utils.get_os_is_windows(): - cls.poll_message_set('This operator is only supported on Windows') + if bpy.app.version >= (2, 90, 0): + cls.poll_message_set('This operator is only supported on Windows') return False wrd = bpy.data.worlds['Lnx'] if len(wrd.lnx_exporterlist) == 0: - cls.poll_message_set('No export configuration exists') + if bpy.app.version >= (2, 90, 0): + cls.poll_message_set('No export configuration exists') return False if wrd.lnx_exporterlist[wrd.lnx_exporterlist_index].lnx_project_target != 'windows-hl': - cls.poll_message_set('This operator only works with the Windows (C) target') + if bpy.app.version >= (2, 90, 0): + cls.poll_message_set('This operator only works with the Windows (C) target') return False return True diff --git a/leenkx/blender/lnx/props_traits.py b/leenkx/blender/lnx/props_traits.py index 48fafe2..b28d8e1 100644 --- a/leenkx/blender/lnx/props_traits.py +++ b/leenkx/blender/lnx/props_traits.py @@ -12,9 +12,18 @@ import bpy.utils.previews import lnx.make as make from lnx.props_traits_props import * 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.write_data as write_data + if lnx.is_reload(__name__): lnx.make = lnx.reload_module(lnx.make) 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""" return tree.bl_idname == 'LnxLogicTreeType' - name: 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"}) - 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"}) - type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM) + name = compatible_prop(StringProperty, name="Name", description="The name of the trait", default="", 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) + 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) - class_name_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"}) - webassembly_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) + 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 = compatible_prop(StringProperty, name="Canvas", 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 = compatible_prop(PointerProperty, type=NodeTree, update=update_trait_group, override={"LIBRARY_OVERRIDABLE"}, poll=poll_node_trees) - lnx_traitpropslist: CollectionProperty(type=LnxTraitPropListItem) - lnx_traitpropslist_index: IntProperty(name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"}) - lnx_traitpropswarnings: CollectionProperty(type=LnxTraitPropWarning) + lnx_traitpropslist = CollectionProperty(type=LnxTraitPropListItem) + lnx_traitpropslist_index = compatible_prop(IntProperty, name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"}) + lnx_traitpropswarnings = CollectionProperty(type=LnxTraitPropWarning) class LNX_UL_TraitList(bpy.types.UIList): """List of traits.""" @@ -475,10 +484,12 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator): # If not, append vertex traversed_indices.append(vertex_index) vertex = export_mesh.vertices[vertex_index].co - # Apply world transform and maintain coordinate system + # Apply world transform tv = world_matrix @ vertex - # Write to OBJ without flipping coordinates - f.write("v %.4f %.4f %.4f\n" % (tv[0], tv[1], tv[2])) + # Write to OBJ + 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 = 0 @@ -522,10 +533,8 @@ class LeenkxGenerateNavmeshButton(bpy.types.Operator): # NavMesh preview settings, cleanup navmesh.name = nav_mesh_name - # Match the original object's transform - navmesh.location = obj.location - navmesh.rotation_euler = obj.rotation_euler - navmesh.scale = (1, 1, 1) # Reset scale to avoid distortion + navmesh.rotation_euler = (0, 0, 0) + navmesh.location = (0, 0, 0) navmesh.lnx_export = False bpy.context.view_layer.objects.active = navmesh @@ -756,7 +765,8 @@ class LnxRefreshObjectScriptsButton(bpy.types.Operator): @classmethod def poll(cls, context): - cls.poll_message_set(LnxRefreshScriptsButton.poll_msg) + if bpy.app.version >= (2, 90, 0): + cls.poll_message_set(LnxRefreshScriptsButton.poll_msg) # Technically we could keep the operator enabled here since # fetch_trait_props() checks for overrides and the operator does # not depend on the current object, but this way the user @@ -1064,11 +1074,10 @@ __REG_CLASSES = ( ) __reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES) - def register(): __reg_classes() - bpy.types.Object.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"}) - bpy.types.Object.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"}) - bpy.types.Scene.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"}) - bpy.types.Scene.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"}) + bpy.types.Object.lnx_traitlist = compatible_prop(CollectionProperty, type=LnxTraitListItem) + bpy.types.Object.lnx_traitlist_index = compatible_prop(IntProperty, name="Index for lnx_traitlist", default=0) + bpy.types.Scene.lnx_traitlist = compatible_prop(CollectionProperty, type=LnxTraitListItem) + bpy.types.Scene.lnx_traitlist_index = compatible_prop(IntProperty, name="Index for lnx_traitlist", default=0) diff --git a/leenkx/blender/lnx/props_traits_props.py b/leenkx/blender/lnx/props_traits_props.py index 4a67507..8761095 100644 --- a/leenkx/blender/lnx/props_traits_props.py +++ b/leenkx/blender/lnx/props_traits_props.py @@ -3,6 +3,14 @@ from bpy.props import * __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 = { "String": "SORTALPHA", "Int": "CHECKBOX_DEHLT", @@ -35,18 +43,19 @@ def filter_objects(item, b_object): class LnxTraitPropWarning(bpy.types.PropertyGroup): - propName: StringProperty(name="Property Name") - warning: StringProperty(name="Warning") + propName = compatible_prop(StringProperty, name="Property Name", override={"LIBRARY_OVERRIDABLE"}) + warning = compatible_prop(StringProperty, name="Warning", override={"LIBRARY_OVERRIDABLE"}) class LnxTraitPropListItem(bpy.types.PropertyGroup): """Group of properties representing an item in the list.""" - name: StringProperty( + name = compatible_prop(StringProperty, name="Name", description="The name of this property", - default="Untitled") + default="Untitled", + override={"LIBRARY_OVERRIDABLE"}) - type: EnumProperty( + type = compatible_prop(EnumProperty, items=( # (Haxe Type, Display Name, Description) ("String", "String", "String Type"), @@ -69,18 +78,18 @@ class LnxTraitPropListItem(bpy.types.PropertyGroup): ) # === VALUES === - value_string: StringProperty(name="Value", default="", override={"LIBRARY_OVERRIDABLE"}) - value_int: IntProperty(name="Value", default=0, override={"LIBRARY_OVERRIDABLE"}) - value_float: FloatProperty(name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"}) - value_bool: BoolProperty(name="Value", default=False, override={"LIBRARY_OVERRIDABLE"}) - value_vec2: FloatVectorProperty(name="Value", size=2, override={"LIBRARY_OVERRIDABLE"}) - value_vec3: FloatVectorProperty(name="Value", size=3, override={"LIBRARY_OVERRIDABLE"}) - value_vec4: FloatVectorProperty(name="Value", size=4, override={"LIBRARY_OVERRIDABLE"}) - value_object: PointerProperty( + value_string = compatible_prop(StringProperty, name="Value", default="", override={"LIBRARY_OVERRIDABLE"}) + value_int = compatible_prop(IntProperty, name="Value", default=0, override={"LIBRARY_OVERRIDABLE"}) + value_float = compatible_prop(FloatProperty, name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"}) + value_bool = compatible_prop(BoolProperty, name="Value", default=False, override={"LIBRARY_OVERRIDABLE"}) + value_vec2 = compatible_prop(FloatVectorProperty, name="Value", size=2, override={"LIBRARY_OVERRIDABLE"}) + value_vec3 = compatible_prop(FloatVectorProperty, name="Value", size=3, override={"LIBRARY_OVERRIDABLE"}) + value_vec4 = compatible_prop(FloatVectorProperty, name="Value", size=4, override={"LIBRARY_OVERRIDABLE"}) + value_object = compatible_prop(PointerProperty, name="Value", type=bpy.types.Object, poll=filter_objects, 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): # Would require way too much effort, so it's out of scope here. diff --git a/leenkx/blender/lnx/props_ui.py b/leenkx/blender/lnx/props_ui.py index b72f1c1..d3d6bd0 100644 --- a/leenkx/blender/lnx/props_ui.py +++ b/leenkx/blender/lnx/props_ui.py @@ -8,6 +8,24 @@ import mathutils import bpy 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 import lnx.api @@ -939,13 +957,13 @@ class LNX_PT_LeenkxExporterPanel(bpy.types.Panel): col = layout.column() 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_compiler_inline') col.prop(wrd, 'lnx_minify_js') 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_optimize_data') col.prop(wrd, 'lnx_asset_compression') @@ -1178,32 +1196,32 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel): layout.use_property_decorate = False 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_cache_build') col.prop(wrd, 'lnx_clear_on_compile') col.prop(wrd, 'lnx_assert_level') 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_stream_scene') col.prop(wrd, 'lnx_loadscreen') 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_materials') col.prop(wrd, 'lnx_deinterleaved_buffers') 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.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True) col.prop(wrd, 'lnx_texture_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.operator('lnx.copy_to_bundled', icon='IMAGE_DATA') @@ -1517,7 +1535,7 @@ class LNX_PT_TopbarPanel(bpy.types.Panel): bl_label = "Leenkx Player" bl_space_type = "VIEW_3D" bl_region_type = "WINDOW" - bl_options = {'INSTANCED'} + bl_options = get_panel_options() def draw_header(self, context): 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. 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.prop(data, prop_condition, text='') sub = row.row() diff --git a/leenkx/blender/lnx/utils.py b/leenkx/blender/lnx/utils.py index 2d9ca78..09f7e98 100644 --- a/leenkx/blender/lnx/utils.py +++ b/leenkx/blender/lnx/utils.py @@ -96,7 +96,7 @@ def convert_image(image, path, file_format='JPEG'): 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 [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 -def get_file_lnx_version_tuple() -> tuple[int]: +def get_file_lnx_version_tuple() -> Tuple[int, ...]: wrd = bpy.data.worlds['Lnx'] 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)) 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: - 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 log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason) diff --git a/leenkx/blender/lnx/utils_vs.py b/leenkx/blender/lnx/utils_vs.py index 2e23246..2c4ba82 100644 --- a/leenkx/blender/lnx/utils_vs.py +++ b/leenkx/blender/lnx/utils_vs.py @@ -5,7 +5,7 @@ import json import os import re import subprocess -from typing import Any, Optional, Callable +from typing import Any, Callable, Dict, List, Optional, Tuple, Union 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) -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: if installed_version['version_major'] == version_major: return installed_version @@ -71,7 +71,7 @@ def get_installed_version(version_major: str, re_fetch=False) -> Optional[dict[s 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: if version[0] == version_major: return { @@ -100,7 +100,7 @@ def fetch_installed_vs(silent=False) -> bool: if not silent: log.warn( 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 @@ -212,14 +212,14 @@ def compile_in_vs(version_major: str, done: Callable[[], None]) -> bool: 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) if name_raw is None: return None 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) if version_raw is 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 -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) -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 # 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') @@ -256,7 +256,7 @@ def _vswhere_get_instances(silent=False) -> Optional[list[dict[str, Any]]]: 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('.')) @@ -281,7 +281,7 @@ def get_vcxproj_path() -> str: 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_min_full = None From 1299306e09c1cf33385e2bbc9dbc30c6b75dfb21 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Sun, 28 Sep 2025 20:01:00 +0000 Subject: [PATCH 4/4] Update leenkx.py --- leenkx.py | 48 ++++++------------------------------------------ 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/leenkx.py b/leenkx.py index ed07a18..e8a7d38 100644 --- a/leenkx.py +++ b/leenkx.py @@ -64,45 +64,8 @@ def get_os(): else: return 'linux' -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(): +def detect_sdk_path(): """Auto-detect the SDK path after Leenkx installation.""" # Do not overwrite the SDK path (this method gets # called after each registration, not after @@ -116,10 +79,10 @@ def detect_sdk_path22(): 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() + if bpy.app.version >= (2, 92, 0): + with bpy.context.temp_override(window=win, screen=win.screen, area=area): + bpy.ops.info.select_all(action='SELECT') + bpy.ops.info.report_copy() area.type = area_type clipboard = bpy.context.window_manager.clipboard @@ -129,6 +92,7 @@ def detect_sdk_path22(): if match: addon_prefs.sdk_path = os.path.dirname(match[-1]) + def get_link_web_server(self): return self.get('link_web_server', 'http://localhost/')