forked from LeenkxTeam/LNXSDK
		
	Merge pull request 'Downward support to 2.8 LTS!!' (#108) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#108
This commit is contained in:
		
							
								
								
									
										26
									
								
								leenkx.py
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								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
 | 
			
		||||
@ -73,9 +79,10 @@ 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()
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -85,6 +92,7 @@ def detect_sdk_path():
 | 
			
		||||
    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/')
 | 
			
		||||
 | 
			
		||||
@ -558,7 +566,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 +848,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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											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 sys
 | 
			
		||||
import types
 | 
			
		||||
import bpy
 | 
			
		||||
 | 
			
		||||
# 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):
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,14 @@ from enum import Enum, unique
 | 
			
		||||
import math
 | 
			
		||||
import os
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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[:]
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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":
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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).
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -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':
 | 
			
		||||
 | 
			
		||||
@ -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':
 | 
			
		||||
 | 
			
		||||
@ -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 == '':
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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])
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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 (
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -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])
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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."""
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user