2970 lines
		
	
	
		
			113 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			2970 lines
		
	
	
		
			113 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import json
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import shutil
 | 
						|
import textwrap
 | 
						|
import mathutils
 | 
						|
 | 
						|
import bpy
 | 
						|
from bpy.props import *
 | 
						|
 | 
						|
from lnx.lightmapper.panels import scene
 | 
						|
 | 
						|
import lnx.api
 | 
						|
import lnx.assets as assets
 | 
						|
from lnx.exporter import LeenkxExporter
 | 
						|
import lnx.log as log
 | 
						|
import lnx.logicnode.replacement
 | 
						|
import lnx.make as make
 | 
						|
import lnx.make_state as state
 | 
						|
import lnx.props as props
 | 
						|
import lnx.props_properties
 | 
						|
import lnx.props_traits
 | 
						|
import lnx.nodes_logic
 | 
						|
import lnx.ui_icons as ui_icons
 | 
						|
import lnx.utils
 | 
						|
import lnx.utils_vs
 | 
						|
import lnx.write_probes
 | 
						|
 | 
						|
if lnx.is_reload(__name__):
 | 
						|
    lnx.api = lnx.reload_module(lnx.api)
 | 
						|
    assets = lnx.reload_module(assets)
 | 
						|
    lnx.exporter = lnx.reload_module(lnx.exporter)
 | 
						|
    from lnx.exporter import LeenkxExporter
 | 
						|
    log = lnx.reload_module(log)
 | 
						|
    lnx.logicnode.replacement = lnx.reload_module(lnx.logicnode.replacement)
 | 
						|
    make = lnx.reload_module(make)
 | 
						|
    state = lnx.reload_module(state)
 | 
						|
    props = lnx.reload_module(props)
 | 
						|
    lnx.props_properties = lnx.reload_module(lnx.props_properties)
 | 
						|
    lnx.props_traits = lnx.reload_module(lnx.props_traits)
 | 
						|
    lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
 | 
						|
    ui_icons = lnx.reload_module(ui_icons)
 | 
						|
    lnx.utils = lnx.reload_module(lnx.utils)
 | 
						|
    lnx.utils_vs = lnx.reload_module(lnx.utils_vs)
 | 
						|
    lnx.write_probes = lnx.reload_module(lnx.write_probes)
 | 
						|
else:
 | 
						|
    lnx.enable_reload(__name__)
 | 
						|
 | 
						|
class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
 | 
						|
    """Menu in object region."""
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "object"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
 | 
						|
        obj = bpy.context.object
 | 
						|
        if obj == None:
 | 
						|
            return
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(obj, 'lnx_export')
 | 
						|
        if not obj.lnx_export:
 | 
						|
            return
 | 
						|
        col.prop(obj, 'lnx_spawn')
 | 
						|
        col.prop(obj, 'lnx_mobile')
 | 
						|
        col.prop(obj, 'lnx_animation_enabled')
 | 
						|
        col.prop(obj, 'lnx_visible_shadow')
 | 
						|
 | 
						|
        if obj.type == 'MESH':
 | 
						|
            layout.prop(obj, 'lnx_instanced')
 | 
						|
            wrd = bpy.data.worlds['Lnx']
 | 
						|
            layout.prop_search(obj, "lnx_tilesheet", wrd, "lnx_tilesheetlist", text="Tilesheet")
 | 
						|
            if obj.lnx_tilesheet != '':
 | 
						|
                selected_ts = None
 | 
						|
                for ts in wrd.lnx_tilesheetlist:
 | 
						|
                    if ts.name == obj.lnx_tilesheet:
 | 
						|
                        selected_ts = ts
 | 
						|
                        break
 | 
						|
                layout.prop_search(obj, "lnx_tilesheet_action", selected_ts, "lnx_tilesheetactionlist", text="Action")
 | 
						|
                layout.prop(obj, "lnx_use_custom_tilesheet_node")
 | 
						|
 | 
						|
        # Properties list
 | 
						|
        lnx.props_properties.draw_properties(layout, obj)
 | 
						|
 | 
						|
        # Lightmapping props
 | 
						|
        if obj.type == "MESH":
 | 
						|
            row = layout.row(align=True)
 | 
						|
            row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_use")
 | 
						|
 | 
						|
            if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
 | 
						|
 | 
						|
                row = layout.row()
 | 
						|
                row.prop(obj.TLM_ObjectProperties, "tlm_use_default_channel")
 | 
						|
 | 
						|
                if not obj.TLM_ObjectProperties.tlm_use_default_channel:
 | 
						|
 | 
						|
                    row = layout.row()
 | 
						|
                    row.prop_search(obj.TLM_ObjectProperties, "tlm_uv_channel", obj.data, "uv_layers", text='UV Channel')
 | 
						|
 | 
						|
                row = layout.row()
 | 
						|
                row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution")
 | 
						|
                if obj.TLM_ObjectProperties.tlm_use_default_channel:
 | 
						|
                    row = layout.row()
 | 
						|
                    row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode")
 | 
						|
                row = layout.row()
 | 
						|
                if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
 | 
						|
 | 
						|
                    if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
 | 
						|
                        row = layout.row()
 | 
						|
                        item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
 | 
						|
                        row.prop_search(obj.TLM_ObjectProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
 | 
						|
                        row = layout.row()
 | 
						|
                    else:
 | 
						|
                        row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
 | 
						|
                        row = layout.row()
 | 
						|
 | 
						|
                else:
 | 
						|
                    row = layout.row()
 | 
						|
                    row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object")
 | 
						|
                    row = layout.row()
 | 
						|
 | 
						|
 | 
						|
                if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
 | 
						|
                    if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
 | 
						|
                        row = layout.row()
 | 
						|
                        item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
 | 
						|
                        row.prop_search(obj.TLM_ObjectProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
 | 
						|
                        row = layout.row()
 | 
						|
 | 
						|
                    else:
 | 
						|
                        row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
 | 
						|
                        row = layout.row()
 | 
						|
 | 
						|
                row.prop(obj.TLM_ObjectProperties, "tlm_mesh_unwrap_margin")
 | 
						|
                row = layout.row()
 | 
						|
                row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filter_override")
 | 
						|
                row = layout.row()
 | 
						|
                if obj.TLM_ObjectProperties.tlm_mesh_filter_override:
 | 
						|
                    row = layout.row(align=True)
 | 
						|
                    row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_mode")
 | 
						|
                    row = layout.row(align=True)
 | 
						|
                    if obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Gaussian":
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_gaussian_strength")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
 | 
						|
                    elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Box":
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_box_strength")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
 | 
						|
                    elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Bilateral":
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_diameter")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_color_deviation")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_coordinate_deviation")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
 | 
						|
                    else:
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_median_kernel", expand=True)
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
 | 
						|
 | 
						|
                #If UV Packer installed
 | 
						|
                if "UV-Packer" in bpy.context.preferences.addons.keys():
 | 
						|
                    row.prop(obj.TLM_ObjectProperties, "tlm_use_uv_packer")
 | 
						|
                    if obj.TLM_ObjectProperties.tlm_use_uv_packer:
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_uv_packer_padding")
 | 
						|
                        row = layout.row(align=True)
 | 
						|
                        row.prop(obj.TLM_ObjectProperties, "tlm_uv_packer_packing_engine")
 | 
						|
 | 
						|
class LNX_PT_ModifiersPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "modifier"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        obj = bpy.context.object
 | 
						|
        if obj == None:
 | 
						|
            return
 | 
						|
        layout.operator("lnx.invalidate_cache")
 | 
						|
 | 
						|
class LNX_PT_ParticlesPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "particle"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        obj = bpy.context.particle_system
 | 
						|
        if obj == None:
 | 
						|
            return
 | 
						|
 | 
						|
        layout.prop(obj.settings, 'lnx_auto_start')
 | 
						|
        layout.prop(obj.settings, 'lnx_is_unique')
 | 
						|
        layout.prop(obj.settings, 'lnx_loop')
 | 
						|
        layout.prop(obj.settings, 'lnx_count_mult')
 | 
						|
 | 
						|
class LNX_PT_PhysicsPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "physics"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        obj = bpy.context.object
 | 
						|
        if obj == None:
 | 
						|
            return
 | 
						|
 | 
						|
        rb = obj.rigid_body
 | 
						|
        if rb is not None:
 | 
						|
            col = layout.column()
 | 
						|
            row = col.row()
 | 
						|
            row.alignment = 'RIGHT'
 | 
						|
 | 
						|
            rb_type = 'Dynamic'
 | 
						|
            if LeenkxExporter.rigid_body_static(rb):
 | 
						|
                rb_type = 'Static'
 | 
						|
            if rb.kinematic:
 | 
						|
                rb_type = 'Kinematic'
 | 
						|
            row.label(text=(f'Rigid Body Export Type: {rb_type}'), icon='AUTO')
 | 
						|
 | 
						|
            layout.prop(obj, 'lnx_rb_linear_factor')
 | 
						|
            layout.prop(obj, 'lnx_rb_angular_factor')
 | 
						|
            layout.prop(obj, 'lnx_rb_angular_friction')
 | 
						|
            layout.prop(obj, 'lnx_rb_trigger')
 | 
						|
            layout.prop(obj, 'lnx_rb_ccd')
 | 
						|
            layout.prop(obj, 'lnx_rb_interpolate')
 | 
						|
 | 
						|
        if obj.soft_body is not None:
 | 
						|
            layout.prop(obj, 'lnx_soft_body_margin')
 | 
						|
 | 
						|
        if obj.rigid_body_constraint is not None:
 | 
						|
            layout.prop(obj, 'lnx_relative_physics_constraint')
 | 
						|
 | 
						|
class LNX_OT_AddArmatureRootMotion(bpy.types.Operator):
 | 
						|
    bl_idname = "lnx.add_root_motion_bone"
 | 
						|
    bl_label = "Add root bone"
 | 
						|
    bl_description = "Add a new bone and set it as the root bone. This may be used for root motion."
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        obj = context.object
 | 
						|
        if obj.mode == 'EDIT':
 | 
						|
            return False
 | 
						|
        armature = obj.data
 | 
						|
        if armature.bones:
 | 
						|
            if armature.bones.active:
 | 
						|
                return True
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        obj = context.object
 | 
						|
        current_mode = obj.mode
 | 
						|
        bpy.ops.object.mode_set(mode='EDIT')
 | 
						|
        armature = obj.data
 | 
						|
        edit_bones = armature.edit_bones
 | 
						|
        current_root_pos = edit_bones.active.head
 | 
						|
        current_root_len = edit_bones.active.length
 | 
						|
        new_root = edit_bones.new("LeenkxRoot")
 | 
						|
        new_root.head = current_root_pos
 | 
						|
        new_root.tail = new_root.head + mathutils.Vector((0.0, 0.0, current_root_len))
 | 
						|
        bpy.ops.object.mode_set(mode=current_mode)
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
# Menu in data region
 | 
						|
class LNX_PT_DataPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "data"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        obj = bpy.context.object
 | 
						|
        if obj == None:
 | 
						|
            return
 | 
						|
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if obj.type == 'CAMERA':
 | 
						|
            layout.prop(obj.data, 'lnx_frustum_culling')
 | 
						|
        elif obj.type == 'MESH' or obj.type == 'FONT' or obj.type == 'META':
 | 
						|
            layout.prop(obj.data, 'lnx_dynamic_usage')
 | 
						|
            layout.operator("lnx.invalidate_cache")
 | 
						|
        elif obj.type == 'LIGHT':
 | 
						|
            layout.prop(obj.data, 'lnx_clip_start')
 | 
						|
            layout.prop(obj.data, 'lnx_clip_end')
 | 
						|
            layout.prop(obj.data, 'lnx_fov')
 | 
						|
            layout.prop(obj.data, 'lnx_shadows_bias')
 | 
						|
        elif obj.type == 'SPEAKER':
 | 
						|
            layout.prop(obj.data, 'lnx_play_on_start')
 | 
						|
            layout.prop(obj.data, 'lnx_loop')
 | 
						|
            layout.prop(obj.data, 'lnx_stream')
 | 
						|
        elif obj.type == 'ARMATURE':
 | 
						|
            layout.prop(obj.data, 'lnx_autobake')
 | 
						|
            layout.prop(obj.data, 'lnx_relative_bone_constraints')
 | 
						|
            if obj.data.bones:
 | 
						|
                if obj.data.bones.active:
 | 
						|
                    layout.label(text='Current Root: ' + obj.data.bones.active.name)
 | 
						|
            layout.operator('lnx.add_root_motion_bone', text='Add new root bone', icon='ADD')
 | 
						|
            pass
 | 
						|
 | 
						|
class LNX_PT_WorldPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx World Properties"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "world"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        world = context.world
 | 
						|
        if world is None:
 | 
						|
            return
 | 
						|
 | 
						|
        layout.prop(world, 'lnx_light_ies_texture')
 | 
						|
        layout.prop(world, 'lnx_light_clouds_texture')
 | 
						|
        layout.prop(world, 'lnx_use_clouds')
 | 
						|
        col = layout.column(align=True)
 | 
						|
        col.enabled = world.lnx_use_clouds
 | 
						|
        col.prop(world, 'lnx_darken_clouds')
 | 
						|
        col.prop(world, 'lnx_clouds_lower')
 | 
						|
        col.prop(world, 'lnx_clouds_upper')
 | 
						|
        col.prop(world, 'lnx_clouds_precipitation')
 | 
						|
        col.prop(world, 'lnx_clouds_secondary')
 | 
						|
        col.prop(world, 'lnx_clouds_wind')
 | 
						|
        col.prop(world, 'lnx_clouds_steps')
 | 
						|
 | 
						|
class LNX_PT_ScenePropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "scene"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        scene = bpy.context.scene
 | 
						|
        if scene == None:
 | 
						|
            return
 | 
						|
        row = layout.row()
 | 
						|
        column = row.column()
 | 
						|
        row.prop(scene, 'lnx_export')
 | 
						|
 | 
						|
class InvalidateCacheButton(bpy.types.Operator):
 | 
						|
    """Delete cached mesh data"""
 | 
						|
    bl_idname = "lnx.invalidate_cache"
 | 
						|
    bl_label = "Invalidate Cache"
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        context.object.data.lnx_cached = False
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class InvalidateMaterialCacheButton(bpy.types.Operator):
 | 
						|
    """Delete cached material data"""
 | 
						|
    bl_idname = "lnx.invalidate_material_cache"
 | 
						|
    bl_label = "Invalidate Cache"
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        context.material.lnx_cached = False
 | 
						|
        context.material.signature = ''
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_OT_NewCustomMaterial(bpy.types.Operator):
 | 
						|
    bl_idname = "lnx.new_custom_material"
 | 
						|
    bl_label = "New Custom Material"
 | 
						|
    bl_description = "Add a new custom material. This will create all the necessary files and folders"
 | 
						|
 | 
						|
    def poll_mat_name(self, context):
 | 
						|
        project_dir = lnx.utils.get_fp()
 | 
						|
        shader_dir_dst = os.path.join(project_dir, 'Shaders')
 | 
						|
        mat_name = lnx.utils.safestr(self.mat_name)
 | 
						|
 | 
						|
        self.mat_exists = os.path.isdir(os.path.join(project_dir, 'Bundled', mat_name))
 | 
						|
 | 
						|
        vert_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
 | 
						|
        frag_exists = os.path.isfile(os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
 | 
						|
        self.shader_exists = vert_exists or frag_exists
 | 
						|
 | 
						|
    mat_name: StringProperty(
 | 
						|
        name='Material Name', description='The name of the new material',
 | 
						|
        default='MyCustomMaterial',
 | 
						|
        update=poll_mat_name)
 | 
						|
    mode: EnumProperty(
 | 
						|
        name='Target RP', description='Choose for which render path mode the new material is created',
 | 
						|
        default='deferred',
 | 
						|
        items=[('deferred', 'Deferred', 'Create the material for a deferred render path'),
 | 
						|
               ('forward', 'Forward', 'Create the material for a forward render path')])
 | 
						|
    mat_exists: BoolProperty(
 | 
						|
        name='Material Already Exists',
 | 
						|
        default=False,
 | 
						|
        options={'HIDDEN', 'SKIP_SAVE'})
 | 
						|
    shader_exists: BoolProperty(
 | 
						|
        name='Shaders Already Exist',
 | 
						|
        default=False,
 | 
						|
        options={'HIDDEN', 'SKIP_SAVE'})
 | 
						|
 | 
						|
    def invoke(self, context, event):
 | 
						|
        if not bpy.data.is_saved:
 | 
						|
            self.report({'INFO'}, "Please save your file first")
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        # Try to set deferred/forward based on the selected render path
 | 
						|
        try:
 | 
						|
            self.mode = 'forward' if lnx.utils.get_rp().rp_renderer == 'Forward' else 'deferred'
 | 
						|
        except IndexError:
 | 
						|
            # No render path, use default (deferred)
 | 
						|
            pass
 | 
						|
 | 
						|
        self.poll_mat_name(context)
 | 
						|
        wm = context.window_manager
 | 
						|
        return wm.invoke_props_dialog(self, width=300)
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
 | 
						|
        layout.prop(self, 'mat_name')
 | 
						|
        layout.prop(self, 'mode', expand=True)
 | 
						|
 | 
						|
        if self.mat_exists:
 | 
						|
            box = layout.box()
 | 
						|
            box.alert = True
 | 
						|
            col = box.column(align=True)
 | 
						|
            col.label(text='A custom material with that name already exists,', icon='ERROR')
 | 
						|
            col.label(text='clicking on \'OK\' will override the material!', icon='BLANK1')
 | 
						|
 | 
						|
        if self.shader_exists:
 | 
						|
            box = layout.box()
 | 
						|
            box.alert = True
 | 
						|
            col = box.column(align=True)
 | 
						|
            col.label(text='Shader file(s) with that name already exists,', icon='ERROR')
 | 
						|
            col.label(text='clicking on \'OK\' will override the shader(s)!', icon='BLANK1')
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if self.mat_name == '':
 | 
						|
            return {'CANCELLED'}
 | 
						|
 | 
						|
        project_dir = lnx.utils.get_fp()
 | 
						|
        shader_dir_src = os.path.join(lnx.utils.get_sdk_path(), 'leenkx', 'Shaders', 'custom_mat_presets')
 | 
						|
        shader_dir_dst = os.path.join(project_dir, 'Shaders')
 | 
						|
        mat_name = lnx.utils.safestr(self.mat_name)
 | 
						|
        mat_dir = os.path.join(project_dir, 'Bundled', mat_name)
 | 
						|
 | 
						|
        os.makedirs(mat_dir, exist_ok=True)
 | 
						|
        os.makedirs(shader_dir_dst, exist_ok=True)
 | 
						|
 | 
						|
        # Shader data
 | 
						|
        if self.mode == 'forward':
 | 
						|
            col_attachments = ['RGBA64']
 | 
						|
            constants = [{'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'}]
 | 
						|
            vertex_elems = [{'name': 'pos', 'data': 'short4norm'}]
 | 
						|
        else:
 | 
						|
            col_attachments = ['RGBA64', 'RGBA64']
 | 
						|
            constants = [
 | 
						|
                {'link': '_worldViewProjectionMatrix', 'name': 'WVP', 'type': 'mat4'},
 | 
						|
                {'link': '_normalMatrix', 'name': 'N', 'type': 'mat3'}
 | 
						|
            ]
 | 
						|
            vertex_elems = [
 | 
						|
                {'name': 'pos', 'data': 'short4norm'},
 | 
						|
                {'name': 'nor', 'data': 'short2norm'}
 | 
						|
            ]
 | 
						|
 | 
						|
        con = {
 | 
						|
            'color_attachments':  col_attachments,
 | 
						|
            'compare_mode': 'less',
 | 
						|
            'constants': constants,
 | 
						|
            'cull_mode': 'clockwise',
 | 
						|
            'depth_write': True,
 | 
						|
            'fragment_shader': f'{mat_name}.frag',
 | 
						|
            'name': 'mesh',
 | 
						|
            'texture_units': [],
 | 
						|
            'vertex_shader': f'{mat_name}.vert',
 | 
						|
            'vertex_elements': vertex_elems
 | 
						|
        }
 | 
						|
        data = {
 | 
						|
            'shader_datas': [{
 | 
						|
                'contexts': [con],
 | 
						|
                'name': f'{mat_name}'
 | 
						|
            }]
 | 
						|
        }
 | 
						|
 | 
						|
        # Save shader data file
 | 
						|
        with open(os.path.join(mat_dir, f'{mat_name}.json'), 'w') as datafile:
 | 
						|
            json.dump(data, datafile, indent=4, sort_keys=True)
 | 
						|
 | 
						|
        # Copy preset shaders to project
 | 
						|
        if self.mode == 'forward':
 | 
						|
            shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
 | 
						|
            shutil.copy(os.path.join(shader_dir_src, 'custom_mat_forward.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
 | 
						|
        else:
 | 
						|
            shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.frag.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.frag.glsl'))
 | 
						|
            shutil.copy(os.path.join(shader_dir_src, 'custom_mat_deferred.vert.glsl'), os.path.join(shader_dir_dst, f'{mat_name}.vert.glsl'))
 | 
						|
 | 
						|
        # True if called from the material properties tab, else it was called from the search menu
 | 
						|
        if hasattr(context, 'material') and context.material is not None:
 | 
						|
            context.material.lnx_custom_material = mat_name
 | 
						|
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_PG_BindTexturesListItem(bpy.types.PropertyGroup):
 | 
						|
    uniform_name: StringProperty(
 | 
						|
        name='Uniform Name',
 | 
						|
        description='The name of the sampler uniform as used in the shader',
 | 
						|
        default='ImageTexture',
 | 
						|
    )
 | 
						|
 | 
						|
    image: PointerProperty(
 | 
						|
        name='Image',
 | 
						|
        type=bpy.types.Image,
 | 
						|
        description='The image to attach to the texture unit',
 | 
						|
    )
 | 
						|
 | 
						|
class LNX_UL_BindTexturesList(bpy.types.UIList):
 | 
						|
    def draw_item(self, context, layout, data, item: LNX_PG_BindTexturesListItem, icon, active_data, active_propname, index):
 | 
						|
        row = layout.row(align=True)
 | 
						|
 | 
						|
        if item.image is not None:
 | 
						|
            row.label(text=item.uniform_name, icon_value=item.image.preview.icon_id)
 | 
						|
        else:
 | 
						|
            row.label(text='<empty>', icon='ERROR')
 | 
						|
 | 
						|
class LNX_OT_BindTexturesListNewItem(bpy.types.Operator):
 | 
						|
    bl_idname = "lnx_bind_textures_list.new_item"
 | 
						|
    bl_label = "Add Texture Binding"
 | 
						|
    bl_description = "Add a new texture binding to the list"
 | 
						|
    bl_options = {'INTERNAL'}
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        mat = context.material
 | 
						|
        if mat is None:
 | 
						|
            return False
 | 
						|
        return True
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        mat = context.material
 | 
						|
        mat.lnx_bind_textures_list.add()
 | 
						|
        mat.lnx_bind_textures_list_index = len(mat.lnx_bind_textures_list) - 1
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_OT_BindTexturesListDeleteItem(bpy.types.Operator):
 | 
						|
    bl_idname = "lnx_bind_textures_list.delete_item"
 | 
						|
    bl_label = "Remove Texture Binding"
 | 
						|
    bl_description = "Delete the selected texture binding from the list"
 | 
						|
    bl_options = {'INTERNAL'}
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        mat = context.material
 | 
						|
        if mat is None:
 | 
						|
            return False
 | 
						|
        return len(mat.lnx_bind_textures_list) > 0
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        mat = context.material
 | 
						|
        lst = mat.lnx_bind_textures_list
 | 
						|
        index = mat.lnx_bind_textures_list_index
 | 
						|
 | 
						|
        if len(lst) <= index:
 | 
						|
            return{'FINISHED'}
 | 
						|
 | 
						|
        lst.remove(index)
 | 
						|
 | 
						|
        if index > 0:
 | 
						|
            index = index - 1
 | 
						|
        mat.lnx_bind_textures_list_index = index
 | 
						|
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_PT_MaterialPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "material"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        mat = bpy.context.material
 | 
						|
        if mat is None:
 | 
						|
            return
 | 
						|
 | 
						|
        layout.prop(mat, 'lnx_cast_shadow')
 | 
						|
        columnb = layout.column()
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        columnb.enabled = len(wrd.lnx_rplist) > 0 and lnx.utils.get_rp().rp_renderer == 'Forward'
 | 
						|
        columnb.prop(mat, 'lnx_receive_shadow')
 | 
						|
        layout.prop(mat, 'lnx_ignore_irradiance')
 | 
						|
        layout.prop(mat, 'lnx_two_sided')
 | 
						|
        columnb = layout.column()
 | 
						|
        columnb.enabled = not mat.lnx_two_sided
 | 
						|
        columnb.prop(mat, 'lnx_cull_mode')
 | 
						|
        layout.prop(mat, 'lnx_material_id')
 | 
						|
        layout.prop(mat, 'lnx_depth_read')
 | 
						|
        layout.prop(mat, 'lnx_overlay')
 | 
						|
        layout.prop(mat, 'lnx_decal')
 | 
						|
        layout.prop(mat, 'lnx_discard')
 | 
						|
        columnb = layout.column()
 | 
						|
        columnb.enabled = mat.lnx_discard
 | 
						|
        columnb.prop(mat, 'lnx_discard_opacity')
 | 
						|
        columnb.prop(mat, 'lnx_discard_opacity_shadows')
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.prop(mat, 'lnx_custom_material')
 | 
						|
        row.operator('lnx.new_custom_material', text='', icon='ADD')
 | 
						|
        layout.prop(mat, 'lnx_skip_context')
 | 
						|
        layout.prop(mat, 'lnx_particle_fade')
 | 
						|
        layout.prop(mat, 'lnx_billboard')
 | 
						|
 | 
						|
        layout.operator("lnx.invalidate_material_cache")
 | 
						|
 | 
						|
class LNX_PT_BindTexturesPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Bind Textures"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "material"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_MaterialPropsPanel"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        mat = context.material
 | 
						|
        if mat is None:
 | 
						|
            return False
 | 
						|
 | 
						|
        return mat.lnx_custom_material != ''
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        mat = bpy.context.material
 | 
						|
        if mat is None:
 | 
						|
            return
 | 
						|
 | 
						|
        row = layout.row(align=True)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.template_list('LNX_UL_BindTexturesList', '', mat, 'lnx_bind_textures_list', mat, 'lnx_bind_textures_list_index')
 | 
						|
 | 
						|
        if mat.lnx_bind_textures_list_index >= 0 and len(mat.lnx_bind_textures_list) > 0:
 | 
						|
            item = mat.lnx_bind_textures_list[mat.lnx_bind_textures_list_index]
 | 
						|
            box = col.box()
 | 
						|
 | 
						|
            if item.image is None:
 | 
						|
                _row = box.row()
 | 
						|
                _row.alert = True
 | 
						|
                _row.alignment = 'RIGHT'
 | 
						|
                _row.label(text="No image selected, skipping export")
 | 
						|
 | 
						|
            box.prop(item, 'uniform_name')
 | 
						|
            box.prop(item, 'image')
 | 
						|
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_bind_textures_list.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_bind_textures_list.delete_item", icon='REMOVE', text="")
 | 
						|
 | 
						|
class LNX_PT_MaterialDriverPropsPanel(bpy.types.Panel):
 | 
						|
    """Per-material properties for custom render path drivers"""
 | 
						|
    bl_label = "Leenkx Driver Properties"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "material"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        mat = context.material
 | 
						|
        if mat is None:
 | 
						|
            return False
 | 
						|
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if wrd.lnx_rplist_index < 0 or len(wrd.lnx_rplist) == 0:
 | 
						|
            return False
 | 
						|
 | 
						|
        if len(lnx.api.drivers) == 0:
 | 
						|
            return False
 | 
						|
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        return rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['draw_mat_props'] is not None
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        lnx.api.drivers[rpdat.rp_driver]['draw_mat_props'](layout, context.material)
 | 
						|
 | 
						|
class LNX_PT_MaterialBlendingPropsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Blending"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "material"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_MaterialPropsPanel"
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        if context.material is None:
 | 
						|
            return
 | 
						|
        self.layout.prop(context.material, 'lnx_blending', text="")
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        mat = bpy.context.material
 | 
						|
        if mat is None:
 | 
						|
            return
 | 
						|
 | 
						|
        flow = layout.grid_flow()
 | 
						|
        flow.enabled = mat.lnx_blending
 | 
						|
        col = flow.column(align=True)
 | 
						|
        col.prop(mat, 'lnx_blending_source')
 | 
						|
        col.prop(mat, 'lnx_blending_destination')
 | 
						|
        col.prop(mat, 'lnx_blending_operation')
 | 
						|
        flow.separator()
 | 
						|
 | 
						|
        col = flow.column(align=True)
 | 
						|
        col.prop(mat, 'lnx_blending_source_alpha')
 | 
						|
        col.prop(mat, 'lnx_blending_destination_alpha')
 | 
						|
        col.prop(mat, 'lnx_blending_operation_alpha')
 | 
						|
 | 
						|
class LNX_PT_LeenkxPlayerPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Player"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.alignment = 'EXPAND'
 | 
						|
        row.scale_y = 1.3
 | 
						|
        if state.proc_play is None and state.proc_build is None:
 | 
						|
            row.operator("lnx.play", icon="PLAY")
 | 
						|
        else:
 | 
						|
            if bpy.app.version < (3, 0, 0):
 | 
						|
                row.operator("lnx.stop", icon="CANCEL", text="")
 | 
						|
            elif bpy.app.version > (3, 0, 0) and bpy.app.version < (4, 3, 2):
 | 
						|
                row.operator("lnx.stop", icon="SEQUENCE_COLOR_01", text="")
 | 
						|
            else:
 | 
						|
                row.operator("lnx.stop", icon="EVENT_MEDIASTOP", text="")
 | 
						|
        row.operator("lnx.clean_menu", icon="BRUSH_DATA")
 | 
						|
 | 
						|
        col = layout.box().column()
 | 
						|
        col.prop(wrd, 'lnx_runtime')
 | 
						|
        col.prop(wrd, 'lnx_play_camera')
 | 
						|
        col.prop(wrd, 'lnx_play_scene')
 | 
						|
        col.prop_search(wrd, 'lnx_play_renderpath', wrd, 'lnx_rplist', text='Render Path')
 | 
						|
 | 
						|
        if log.num_warnings > 0:
 | 
						|
            box = layout.box()
 | 
						|
            box.alert = True
 | 
						|
 | 
						|
            col = box.column(align=True)
 | 
						|
            warnings = 'warnings' if log.num_warnings > 1 else 'warning'
 | 
						|
            col.label(text=f'{log.num_warnings} {warnings} occurred during compilation!', icon='ERROR')
 | 
						|
            # Blank icon to achieve the same indentation as the line before
 | 
						|
            # prevent showing "open console" twice:
 | 
						|
            if log.num_errors == 0:
 | 
						|
                col.label(text='Please open the console to get more information.', icon='BLANK1')
 | 
						|
 | 
						|
        if log.num_errors > 0:
 | 
						|
            box = layout.box()
 | 
						|
            box.alert = True
 | 
						|
            # Less spacing between lines
 | 
						|
            col = box.column(align=True)
 | 
						|
            errors = 'errors' if log.num_errors > 1 else 'error'
 | 
						|
            col.label(text=f'{log.num_errors} {errors} occurred during compilation!', icon='CANCEL')
 | 
						|
            # Blank icon to achieve the same indentation as the line before
 | 
						|
            col.label(text='Please open the console to get more information.', icon='BLANK1')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Exporter"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.alignment = 'EXPAND'
 | 
						|
        row.scale_y = 1.3
 | 
						|
        row.operator("lnx.build_project", icon="MOD_BUILD")
 | 
						|
        # row.operator("lnx.patch_project")
 | 
						|
        row.operator("lnx.publish_project", icon="EXPORT")
 | 
						|
 | 
						|
        rows = 2
 | 
						|
        if len(wrd.lnx_exporterlist) > 1:
 | 
						|
            rows = 4
 | 
						|
        row = layout.row()
 | 
						|
        row.template_list("LNX_UL_ExporterList", "The_List", wrd, "lnx_exporterlist", wrd, "lnx_exporterlist_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_exporterlist.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_exporterlist.delete_item", icon='REMOVE', text="")
 | 
						|
        col.menu("LNX_MT_ExporterListSpecials", icon='DOWNARROW_HLT', text="")
 | 
						|
 | 
						|
        if len(wrd.lnx_exporterlist) > 1:
 | 
						|
            col.separator()
 | 
						|
            op = col.operator("lnx_exporterlist.move_item", icon='TRIA_UP', text="")
 | 
						|
            op.direction = 'UP'
 | 
						|
            op = col.operator("lnx_exporterlist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
            op.direction = 'DOWN'
 | 
						|
 | 
						|
        if wrd.lnx_exporterlist_index >= 0 and len(wrd.lnx_exporterlist) > 0:
 | 
						|
            item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | 
						|
            box = layout.box().column()
 | 
						|
            box.prop(item, 'lnx_project_target')
 | 
						|
            box.prop(item, 'lnx_project_khamake')
 | 
						|
            box.prop(item, lnx.utils.target_to_gapi(item.lnx_project_target))
 | 
						|
            box.prop_search(item, "lnx_project_rp", wrd, "lnx_rplist", text="Render Path")
 | 
						|
            box.prop_search(item, 'lnx_project_scene', bpy.data, 'scenes', text='Scene')
 | 
						|
            layout.separator()
 | 
						|
 | 
						|
        col = layout.column(align=True)
 | 
						|
        col.prop(wrd, 'lnx_project_name')
 | 
						|
        col.prop(wrd, 'lnx_project_package')
 | 
						|
        col.prop(wrd, 'lnx_project_bundle')
 | 
						|
 | 
						|
        col = layout.column(align=True)
 | 
						|
        col.prop(wrd, 'lnx_project_version')
 | 
						|
        col.prop(wrd, 'lnx_project_version_autoinc')
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(wrd, 'lnx_project_icon')
 | 
						|
 | 
						|
        col = layout.column(heading='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.prop(wrd, 'lnx_minimize')
 | 
						|
        col.prop(wrd, 'lnx_optimize_data')
 | 
						|
        col.prop(wrd, 'lnx_asset_compression')
 | 
						|
        col.prop(wrd, 'lnx_single_data_file')
 | 
						|
 | 
						|
class ExporterTargetSettingsMixin:
 | 
						|
    """Mixin for common exporter setting subpanel functionality.
 | 
						|
 | 
						|
    Panels that inherit from this mixin need to have a lnx_target
 | 
						|
    variable for polling."""
 | 
						|
    bl_space_type = 'PROPERTIES'
 | 
						|
    bl_region_type = 'WINDOW'
 | 
						|
    bl_context = 'render'
 | 
						|
    bl_parent_id = 'LNX_PT_LeenkxExporterPanel'
 | 
						|
 | 
						|
    # Override this in sub classes
 | 
						|
    lnx_panel = ''
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if (len(wrd.lnx_exporterlist) > 0) and (wrd.lnx_exporterlist_index >= 0):
 | 
						|
            item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | 
						|
            return item.lnx_project_target == cls.lnx_target
 | 
						|
        return False
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        self.layout.label(text='', icon='SETTINGS')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterAndroidSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel):
 | 
						|
    bl_label = "Android Settings"
 | 
						|
    lnx_target = 'android-hl'  # See ExporterTargetSettingsMixin
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(wrd, 'lnx_winorient')
 | 
						|
        col.prop(wrd, 'lnx_project_android_sdk_min')
 | 
						|
        col.prop(wrd, 'lnx_project_android_sdk_target')
 | 
						|
        col.prop(wrd, 'lnx_project_android_sdk_compile')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterAndroidPermissionsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Permissions"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxExporterAndroidSettingsPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        # Permission
 | 
						|
        row = layout.row()
 | 
						|
        rows = 2
 | 
						|
        if len(wrd.lnx_exporter_android_permission_list) > 1:
 | 
						|
            rows = 4
 | 
						|
        row.template_list("LNX_UL_Exporter_AndroidPermissionList", "The_List", wrd, "lnx_exporter_android_permission_list", wrd, "lnx_exporter_android_permission_list_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_exporter_android_permission_list.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_exporter_android_permission_list.delete_item", icon='REMOVE', text="")
 | 
						|
        row = layout.row()
 | 
						|
 | 
						|
        if wrd.lnx_exporter_android_permission_list_index >= 0 and len(wrd.lnx_exporter_android_permission_list) > 0:
 | 
						|
            item = wrd.lnx_exporter_android_permission_list[wrd.lnx_exporter_android_permission_list_index]
 | 
						|
            row = layout.row()
 | 
						|
            row.prop(item, 'lnx_android_permissions')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterAndroidAbiPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Android ABI Filters"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = { 'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxExporterAndroidSettingsPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        # ABIs
 | 
						|
        row = layout.row()
 | 
						|
        rows = 2
 | 
						|
        if len(wrd.lnx_exporter_android_abi_list) > 1:
 | 
						|
            rows = 4
 | 
						|
        row.template_list("LNX_UL_Exporter_AndroidAbiList", "The_List", wrd, "lnx_exporter_android_abi_list", wrd, "lnx_exporter_android_abi_list_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_exporter_android_abi_list.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_exporter_android_abi_list.delete_item", icon='REMOVE', text="")
 | 
						|
        row = layout.row()
 | 
						|
 | 
						|
        if wrd.lnx_exporter_android_abi_list_index >= 0 and len(wrd.lnx_exporter_android_abi_list) > 0:
 | 
						|
            item = wrd.lnx_exporter_android_abi_list[wrd.lnx_exporter_android_abi_list_index]
 | 
						|
            row = layout.row()
 | 
						|
            row.prop(item, 'lnx_android_abi')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterAndroidBuildAPKPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Building APK"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxExporterAndroidSettingsPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        path = lnx.utils.get_android_sdk_root_path()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = len(path) > 0
 | 
						|
        row.prop(wrd, 'lnx_project_android_build_apk')
 | 
						|
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = wrd.lnx_project_android_build_apk
 | 
						|
        row.prop(wrd, 'lnx_project_android_rename_apk')
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = wrd.lnx_project_android_build_apk and len(lnx.utils.get_android_apk_copy_path()) > 0
 | 
						|
        row.prop(wrd, 'lnx_project_android_copy_apk')
 | 
						|
 | 
						|
        row = col.row(align=True)
 | 
						|
        row.prop(wrd, 'lnx_project_android_list_avd')
 | 
						|
        sub = row.column(align=True)
 | 
						|
        sub.enabled = len(path) > 0
 | 
						|
        sub.operator('lnx.update_list_android_emulator', text='', icon='FILE_REFRESH')
 | 
						|
        sub = row.column(align=True)
 | 
						|
        sub.enabled = len(path) > 0 and len(lnx.utils.get_android_emulator_name()) > 0
 | 
						|
        sub.operator('lnx.run_android_emulator', text='', icon='PLAY')
 | 
						|
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = lnx.utils.get_project_android_build_apk() and len(lnx.utils.get_android_emulator_name()) > 0
 | 
						|
        row.prop(wrd, 'lnx_project_android_run_avd')
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterHTML5SettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel):
 | 
						|
    bl_label = "HTML5 Settings"
 | 
						|
    lnx_target = 'html5'  # See ExporterTargetSettingsMixin
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(wrd, 'lnx_project_html5_popupmenu_in_browser')
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = len(lnx.utils.get_html5_copy_path()) > 0
 | 
						|
        row.prop(wrd, 'lnx_project_html5_copy')
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = len(lnx.utils.get_html5_copy_path()) > 0 and wrd.lnx_project_html5_copy and len(lnx.utils.get_link_web_server()) > 0
 | 
						|
        row.prop(wrd, 'lnx_project_html5_start_browser')
 | 
						|
 | 
						|
 | 
						|
class LNX_PT_LeenkxExporterWindowsSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel):
 | 
						|
    bl_label = "Windows Settings"
 | 
						|
    lnx_target = 'windows-hl'  # See ExporterTargetSettingsMixin
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        is_windows = lnx.utils.get_os_is_windows()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(wrd, 'lnx_project_win_list_vs')
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = is_windows
 | 
						|
        row.prop(wrd, 'lnx_project_win_build', text='After Publish')
 | 
						|
 | 
						|
        layout = layout.column()
 | 
						|
        layout.enabled = is_windows
 | 
						|
 | 
						|
        if is_windows and wrd.lnx_project_win_build != 'nothing' and not lnx.utils_vs.is_version_installed(wrd.lnx_project_win_list_vs):
 | 
						|
            box = draw_error_box(
 | 
						|
                layout,
 | 
						|
                'The selected version of Visual Studio could not be found and'
 | 
						|
                ' may not be installed. The "After Publish" action may not work'
 | 
						|
                ' as intended.'
 | 
						|
            )
 | 
						|
            box.operator('lnx.update_list_installed_vs', icon='FILE_REFRESH')
 | 
						|
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.enabled = wrd.lnx_project_win_build.startswith('compile')
 | 
						|
        col.prop(wrd, 'lnx_project_win_build_mode')
 | 
						|
        col.prop(wrd, 'lnx_project_win_build_arch')
 | 
						|
        col.prop(wrd, 'lnx_project_win_build_log')
 | 
						|
        col.prop(wrd, 'lnx_project_win_build_cpu')
 | 
						|
        col.prop(wrd, 'lnx_project_win_build_open')
 | 
						|
 | 
						|
class LNX_PT_LeenkxProjectPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Project"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.operator("lnx.open_editor", icon="DESKTOP")
 | 
						|
        row.operator("lnx.open_project_folder", icon="FILE_FOLDER")
 | 
						|
 | 
						|
class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Flags"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxProjectPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        col = layout.column(heading='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.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.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')
 | 
						|
        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.prop(wrd, 'lnx_copy_override')
 | 
						|
        col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
 | 
						|
 | 
						|
class LNX_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel):
 | 
						|
    bl_label = "Debug Console"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_ProjectFlagsPanel"
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        self.layout.prop(wrd, 'lnx_debug_console', text='')
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        col = layout.column()
 | 
						|
        col.enabled = wrd.lnx_debug_console
 | 
						|
        col.prop(wrd, 'lnx_debug_console_position')
 | 
						|
        col.prop(wrd, 'lnx_debug_console_scale')
 | 
						|
        col.prop(wrd, 'lnx_debug_console_visible')
 | 
						|
        col.prop(wrd, 'lnx_debug_console_trace_pos')
 | 
						|
 | 
						|
class LNX_PT_ProjectWindowPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Window"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxProjectPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        layout.prop(wrd, 'lnx_winmode')
 | 
						|
 | 
						|
        col = layout.column(align=True)
 | 
						|
        col.prop(wrd, 'lnx_winresize')
 | 
						|
        sub = col.column()
 | 
						|
        sub.enabled = wrd.lnx_winresize
 | 
						|
        sub.prop(wrd, 'lnx_winmaximize')
 | 
						|
        col.enabled = True
 | 
						|
        col.prop(wrd, 'lnx_winminimize')
 | 
						|
 | 
						|
        layout.prop(wrd, 'lnx_vsync')
 | 
						|
 | 
						|
class LNX_PT_ProjectModulesPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Modules"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_LeenkxProjectPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        layout.prop(wrd, 'lnx_audio')
 | 
						|
        layout.prop(wrd, 'lnx_physics')
 | 
						|
        if wrd.lnx_physics != 'Disabled':
 | 
						|
            layout.prop(wrd, 'lnx_physics_engine')
 | 
						|
        layout.prop(wrd, 'lnx_navigation')
 | 
						|
        if wrd.lnx_navigation != 'Disabled':
 | 
						|
            layout.prop(wrd, 'lnx_navigation_engine')
 | 
						|
        layout.prop(wrd, 'lnx_ui')
 | 
						|
        layout.prop(wrd, 'lnx_network')
 | 
						|
 | 
						|
        layout.prop_search(wrd, 'lnx_khafile', bpy.data, 'texts')
 | 
						|
        layout.prop(wrd, 'lnx_project_root')
 | 
						|
 | 
						|
class LnxVirtualInputPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Virtual Input"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
 | 
						|
class LeenkxPlayButton(bpy.types.Operator):
 | 
						|
    '''Launch player in new window'''
 | 
						|
    bl_idname = 'lnx.play'
 | 
						|
    bl_label = 'Play'
 | 
						|
 | 
						|
    def invoke(self, context, event):
 | 
						|
        if event.shift:
 | 
						|
            state.is_play = True
 | 
						|
            make.build_success()
 | 
						|
            return{'FINISHED'}
 | 
						|
        return self.execute(context)
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        if state.proc_build is not None:
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        lnx.utils.check_blender_version(self)
 | 
						|
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if not lnx.utils.check_sdkpath(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        lnx.utils.check_projectpath(None)
 | 
						|
 | 
						|
        lnx.utils.check_default_props()
 | 
						|
 | 
						|
        assets.invalidate_enabled = False
 | 
						|
        if wrd.lnx_clear_on_compile:
 | 
						|
            os.system("cls")
 | 
						|
        make.play()
 | 
						|
        assets.invalidate_enabled = True
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LeenkxStopButton(bpy.types.Operator):
 | 
						|
    '''Stop currently running player'''
 | 
						|
    bl_idname = 'lnx.stop'
 | 
						|
    bl_label = 'Stop'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if state.proc_play != None:
 | 
						|
            state.proc_play.terminate()
 | 
						|
            state.proc_play = None
 | 
						|
        elif state.proc_build != None:
 | 
						|
            state.proc_build.terminate()
 | 
						|
            state.proc_build = None
 | 
						|
 | 
						|
        lnx.write_probes.check_last_cmft_time()
 | 
						|
 | 
						|
        return {'FINISHED'}
 | 
						|
 | 
						|
class LeenkxBuildProjectButton(bpy.types.Operator):
 | 
						|
    """Build and compile project"""
 | 
						|
    bl_idname = 'lnx.build_project'
 | 
						|
    bl_label = 'Build'
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        return wrd.lnx_exporterlist_index >= 0 and len(wrd.lnx_exporterlist) > 0
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        lnx.utils.check_blender_version(self)
 | 
						|
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if not lnx.utils.check_sdkpath(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        lnx.utils.check_projectpath(self)
 | 
						|
 | 
						|
        lnx.utils.check_default_props()
 | 
						|
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | 
						|
        if item.lnx_project_rp == '':
 | 
						|
            item.lnx_project_rp = wrd.lnx_rplist[wrd.lnx_rplist_index].name
 | 
						|
        if item.lnx_project_scene == None:
 | 
						|
            item.lnx_project_scene = context.scene
 | 
						|
        # Assume unique rp names
 | 
						|
        rplist_index = wrd.lnx_rplist_index
 | 
						|
        for i in range(0, len(wrd.lnx_rplist)):
 | 
						|
            if wrd.lnx_rplist[i].name == item.lnx_project_rp:
 | 
						|
                wrd.lnx_rplist_index = i
 | 
						|
                break
 | 
						|
        assets.invalidate_shader_cache(None, None)
 | 
						|
        assets.invalidate_enabled = False
 | 
						|
        if wrd.lnx_clear_on_compile:
 | 
						|
            os.system("cls")
 | 
						|
        make.build(item.lnx_project_target, is_export=True)
 | 
						|
        make.compile()
 | 
						|
        wrd.lnx_rplist_index = rplist_index
 | 
						|
        assets.invalidate_enabled = True
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LeenkxPublishProjectButton(bpy.types.Operator):
 | 
						|
    """Build project ready for publishing."""
 | 
						|
    bl_idname = 'lnx.publish_project'
 | 
						|
    bl_label = 'Publish'
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        return wrd.lnx_exporterlist_index >= 0 and len(wrd.lnx_exporterlist) > 0
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        lnx.utils.check_blender_version(self)
 | 
						|
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if not lnx.utils.check_sdkpath(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        self.report({'INFO'}, 'Publishing project, check console for details.')
 | 
						|
 | 
						|
        lnx.utils.check_projectpath(self)
 | 
						|
 | 
						|
        lnx.utils.check_default_props()
 | 
						|
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        item = wrd.lnx_exporterlist[wrd.lnx_exporterlist_index]
 | 
						|
        if item.lnx_project_rp == '':
 | 
						|
            item.lnx_project_rp = wrd.lnx_rplist[wrd.lnx_rplist_index].name
 | 
						|
        if item.lnx_project_scene == None:
 | 
						|
            item.lnx_project_scene = context.scene
 | 
						|
        # Assume unique rp names
 | 
						|
        rplist_index = wrd.lnx_rplist_index
 | 
						|
        for i in range(0, len(wrd.lnx_rplist)):
 | 
						|
            if wrd.lnx_rplist[i].name == item.lnx_project_rp:
 | 
						|
                wrd.lnx_rplist_index = i
 | 
						|
                break
 | 
						|
 | 
						|
        make.clean()
 | 
						|
        assets.invalidate_enabled = False
 | 
						|
        if wrd.lnx_clear_on_compile:
 | 
						|
            os.system("cls")
 | 
						|
        make.build(item.lnx_project_target, is_publish=True, is_export=True)
 | 
						|
        make.compile()
 | 
						|
        wrd.lnx_rplist_index = rplist_index
 | 
						|
        assets.invalidate_enabled = True
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LeenkxOpenProjectFolderButton(bpy.types.Operator):
 | 
						|
    '''Open project folder'''
 | 
						|
    bl_idname = 'lnx.open_project_folder'
 | 
						|
    bl_label = 'Project Folder'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        lnx.utils.open_folder(lnx.utils.get_fp())
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LeenkxOpenEditorButton(bpy.types.Operator):
 | 
						|
    '''Launch this project in the IDE'''
 | 
						|
    bl_idname = 'lnx.open_editor'
 | 
						|
    bl_label = 'Code Editor'
 | 
						|
    bl_description = 'Open Project in IDE'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        lnx.utils.check_default_props()
 | 
						|
 | 
						|
        if not os.path.exists(lnx.utils.get_fp() + "/khafile.js"):
 | 
						|
            print('Generating Krom project for IDE build configuration')
 | 
						|
            make.build('krom')
 | 
						|
 | 
						|
        lnx.utils.open_editor()
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class CleanMenu(bpy.types.Menu):
 | 
						|
    bl_label = "Ok?"
 | 
						|
    bl_idname = "OBJECT_MT_clean_menu"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.operator("lnx.clean_project")
 | 
						|
 | 
						|
class CleanButtonMenu(bpy.types.Operator):
 | 
						|
    '''Clean cached data'''
 | 
						|
    bl_label = "Clean"
 | 
						|
    bl_idname = "lnx.clean_menu"
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        bpy.ops.wm.call_menu(name=CleanMenu.bl_idname)
 | 
						|
        return {"FINISHED"}
 | 
						|
 | 
						|
class LeenkxCleanProjectButton(bpy.types.Operator):
 | 
						|
    '''Delete all cached project data'''
 | 
						|
    bl_idname = 'lnx.clean_project'
 | 
						|
    bl_label = 'Clean Project'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        make.clean()
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
def draw_view3d_header(self, context):
 | 
						|
    if state.proc_build is not None:
 | 
						|
        self.layout.label(text='Compiling..')
 | 
						|
    elif log.info_text != '':
 | 
						|
        self.layout.label(text=log.info_text)
 | 
						|
 | 
						|
def draw_view3d_object_menu(self, context):
 | 
						|
    self.layout.separator()
 | 
						|
    self.layout.operator_context = 'INVOKE_DEFAULT'
 | 
						|
    self.layout.operator('lnx.copy_traits_to_active')
 | 
						|
 | 
						|
class LNX_PT_TopbarPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Player"
 | 
						|
    bl_space_type = "VIEW_3D"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_options = {'INSTANCED'}
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        row = self.layout.row(align=True)
 | 
						|
        if state.proc_play is None and state.proc_build is None:
 | 
						|
            row.operator("lnx.play", icon="PLAY", text="")
 | 
						|
        else:
 | 
						|
            if bpy.app.version < (3, 0, 0):
 | 
						|
                row.operator("lnx.stop", icon="CANCEL", text="")
 | 
						|
            elif bpy.app.version > (3, 0, 0) and bpy.app.version < (4, 3, 2):
 | 
						|
                row.operator("lnx.stop", icon="SEQUENCE_COLOR_01", text="")
 | 
						|
            else:
 | 
						|
                row.operator("lnx.stop", icon="EVENT_MEDIASTOP", text="")
 | 
						|
        row.operator("lnx.clean_menu", icon="BRUSH_DATA", text="")
 | 
						|
        row.operator("lnx.open_editor", icon="DESKTOP", text="")
 | 
						|
        row.operator("lnx.open_project_folder", icon="FILE_FOLDER", text="")
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        col = self.layout.column()
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        col.label(text="Leenkx Launch")
 | 
						|
        col.separator()
 | 
						|
 | 
						|
        col.prop(wrd, 'lnx_runtime')
 | 
						|
        col.prop(wrd, 'lnx_play_camera')
 | 
						|
        col.prop(wrd, 'lnx_play_scene')
 | 
						|
        col.prop_search(wrd, 'lnx_play_renderpath', wrd, 'lnx_rplist', text='Render Path')
 | 
						|
        col.prop(wrd, 'lnx_debug_console', text="Debug Console")
 | 
						|
 | 
						|
def draw_space_topbar(self, context):
 | 
						|
    # for some blender reasons, topbar is instanced twice. this avoids doubling the panel
 | 
						|
    if context.region.alignment == 'RIGHT':
 | 
						|
        self.layout.popover(panel="LNX_PT_TopbarPanel", text="")
 | 
						|
 | 
						|
class LNX_PT_RenderPathPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Render Path"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        rows = 2
 | 
						|
        if len(wrd.lnx_rplist) > 1:
 | 
						|
            rows = 4
 | 
						|
        row = layout.row()
 | 
						|
        row.template_list("LNX_UL_RPList", "The_List", wrd, "lnx_rplist", wrd, "lnx_rplist_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_rplist.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_rplist.delete_item", icon='REMOVE', text="")
 | 
						|
 | 
						|
        if len(wrd.lnx_rplist) > 1:
 | 
						|
            col.separator()
 | 
						|
            op = col.operator("lnx_rplist.move_item", icon='TRIA_UP', text="")
 | 
						|
            op.direction = 'UP'
 | 
						|
            op = col.operator("lnx_rplist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
            op.direction = 'DOWN'
 | 
						|
 | 
						|
        if wrd.lnx_rplist_index < 0 or len(wrd.lnx_rplist) == 0:
 | 
						|
            return
 | 
						|
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        if len(lnx.api.drivers) > 0:
 | 
						|
            layout.prop_search(rpdat, "rp_driver", wrd, "rp_driver_list", text="Driver")
 | 
						|
            layout.separator()
 | 
						|
            if rpdat.rp_driver != 'Leenkx' and lnx.api.drivers[rpdat.rp_driver]['draw_props'] != None:
 | 
						|
                lnx.api.drivers[rpdat.rp_driver]['draw_props'](layout)
 | 
						|
                return
 | 
						|
 | 
						|
class LNX_PT_RenderPathRendererPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Renderer"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.prop(rpdat, 'rp_renderer')
 | 
						|
        if rpdat.rp_renderer == 'Forward':
 | 
						|
            layout.prop(rpdat, 'rp_depthprepass')
 | 
						|
        layout.prop(rpdat, 'lnx_material_model')
 | 
						|
        layout.prop(rpdat, 'rp_translucency_state')
 | 
						|
        layout.prop(rpdat, 'rp_overlays_state')
 | 
						|
        layout.prop(rpdat, 'rp_decals_state')
 | 
						|
        layout.prop(rpdat, 'rp_blending_state')
 | 
						|
        layout.prop(rpdat, 'rp_depth_texture_state')
 | 
						|
        layout.prop(rpdat, 'rp_draw_order')
 | 
						|
        layout.prop(rpdat, 'lnx_samples_per_pixel')
 | 
						|
        layout.prop(rpdat, 'lnx_texture_filter')
 | 
						|
        layout.prop(rpdat, 'rp_sss_state')
 | 
						|
        col = layout.column()
 | 
						|
        col.enabled = rpdat.rp_sss_state != 'Off'
 | 
						|
        col.prop(rpdat, 'lnx_sss_width')
 | 
						|
        layout.prop(rpdat, 'lnx_rp_displacement')
 | 
						|
        if rpdat.lnx_rp_displacement == 'Tessellation':
 | 
						|
            layout.label(text='Mesh')
 | 
						|
            layout.prop(rpdat, 'lnx_tess_mesh_inner')
 | 
						|
            layout.prop(rpdat, 'lnx_tess_mesh_outer')
 | 
						|
            layout.label(text='Shadow')
 | 
						|
            layout.prop(rpdat, 'lnx_tess_shadows_inner')
 | 
						|
            layout.prop(rpdat, 'lnx_tess_shadows_outer')
 | 
						|
 | 
						|
        layout.prop(rpdat, 'lnx_particles')
 | 
						|
        layout.separator(factor=0.1)
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_skin')
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.lnx_skin == 'On'
 | 
						|
        col.prop(rpdat, 'lnx_use_armature_deform_only')
 | 
						|
        col.prop(rpdat, 'lnx_skin_max_bones_auto')
 | 
						|
        row = col.row()
 | 
						|
        row.enabled = not rpdat.lnx_skin_max_bones_auto
 | 
						|
        row.prop(rpdat, 'lnx_skin_max_bones')
 | 
						|
        layout.separator(factor=0.1)
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_morph_target')
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.lnx_morph_target == 'On'
 | 
						|
        layout.separator(factor=0.1)
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_hdr")
 | 
						|
        col.prop(rpdat, "rp_stereo")
 | 
						|
        col.prop(rpdat, 'lnx_culling')
 | 
						|
        col.prop(rpdat, 'rp_pp')
 | 
						|
 | 
						|
 | 
						|
class LNX_PT_RenderPathShadowsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Shadows"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        self.layout.prop(rpdat, "rp_shadows", text="")
 | 
						|
 | 
						|
    def compute_subdivs(self, max, subdivs):
 | 
						|
        l = [max]
 | 
						|
        for i in range(subdivs - 1):
 | 
						|
            l.append(int(max / 2))
 | 
						|
            max = max / 2
 | 
						|
        return l
 | 
						|
 | 
						|
    def tiles_per_light_type(self, rpdat: lnx.props_renderpath.LnxRPListItem, light_type: str) -> int:
 | 
						|
        if light_type == 'point':
 | 
						|
            return 6
 | 
						|
        elif light_type == 'spot':
 | 
						|
            return 1
 | 
						|
        else:
 | 
						|
            return int(rpdat.rp_shadowmap_cascades)
 | 
						|
 | 
						|
    def lights_number_atlas(self, rpdat: lnx.props_renderpath.LnxRPListItem, atlas_size: int, shadowmap_size: int, light_type: str) -> int:
 | 
						|
        '''Compute number lights that could fit in an atlas'''
 | 
						|
        lights = atlas_size / shadowmap_size
 | 
						|
        lights *= lights / self.tiles_per_light_type(rpdat, light_type)
 | 
						|
        return int(lights)
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.enabled = rpdat.rp_shadows
 | 
						|
        col = layout.column()
 | 
						|
        col.enabled = not rpdat.rp_shadowmap_atlas_single_map or not rpdat.rp_shadowmap_atlas
 | 
						|
        col.prop(rpdat, 'rp_shadowmap_cube')
 | 
						|
        layout.prop(rpdat, 'rp_shadowmap_cascade')
 | 
						|
        layout.prop(rpdat, 'rp_shadowmap_cascades')
 | 
						|
        layout.prop(rpdat, 'rp_shadowmap_transparent')
 | 
						|
        col = layout.column()
 | 
						|
        col2 = col.column()
 | 
						|
        col2.enabled = rpdat.rp_shadowmap_cascades != '1'
 | 
						|
        col2.prop(rpdat, 'lnx_shadowmap_split')
 | 
						|
        col.prop(rpdat, 'lnx_shadowmap_bounds')
 | 
						|
        col.prop(rpdat, 'lnx_pcfsize')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        layout.prop(rpdat, 'rp_shadowmap_atlas')
 | 
						|
        colatlas = layout.column()
 | 
						|
        colatlas.enabled = rpdat.rp_shadowmap_atlas
 | 
						|
        colatlas.prop(rpdat, 'rp_max_lights')
 | 
						|
        colatlas.prop(rpdat, 'rp_max_lights_cluster')
 | 
						|
        colatlas.prop(rpdat, 'rp_shadowmap_atlas_lod')
 | 
						|
 | 
						|
        colatlas_lod = colatlas.column()
 | 
						|
        colatlas_lod.enabled = rpdat.rp_shadowmap_atlas_lod
 | 
						|
        colatlas_lod.prop(rpdat, 'rp_shadowmap_atlas_lod_subdivisions')
 | 
						|
 | 
						|
        colatlas_lod_info = colatlas_lod.row()
 | 
						|
        colatlas_lod_info.alignment = 'RIGHT'
 | 
						|
        subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions))
 | 
						|
        subdiv_text = "Subdivisions for spot lights: " + ', '.join(map(str, subdivs_list))
 | 
						|
        colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH")
 | 
						|
 | 
						|
        if not rpdat.rp_shadowmap_atlas_single_map:
 | 
						|
            colatlas_lod_info = colatlas_lod.row()
 | 
						|
            colatlas_lod_info.alignment = 'RIGHT'
 | 
						|
            subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cube), int(rpdat.rp_shadowmap_atlas_lod_subdivisions))
 | 
						|
            subdiv_text = "Subdivisions for point lights: " + ', '.join(map(str, subdivs_list))
 | 
						|
            colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH")
 | 
						|
 | 
						|
        size_warning = int(rpdat.rp_shadowmap_cascade) > 2048 or int(rpdat.rp_shadowmap_cube) > 2048
 | 
						|
 | 
						|
        colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map')
 | 
						|
        # show size for single texture
 | 
						|
        if rpdat.rp_shadowmap_atlas_single_map:
 | 
						|
            colatlas_single = colatlas.column()
 | 
						|
            colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size')
 | 
						|
            if rpdat.rp_shadowmap_atlas_max_size != '':
 | 
						|
                atlas_size = int(rpdat.rp_shadowmap_atlas_max_size)
 | 
						|
                shadowmap_size = int(rpdat.rp_shadowmap_cascade)
 | 
						|
 | 
						|
                if shadowmap_size > 2048:
 | 
						|
                    size_warning = True
 | 
						|
 | 
						|
                point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point')
 | 
						|
                spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot')
 | 
						|
                dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun')
 | 
						|
 | 
						|
                col = colatlas_single.row()
 | 
						|
                col.alignment = 'RIGHT'
 | 
						|
                col.label(text=f'Enough space for { point_lights } point lights or { spot_lights } spot lights or { dir_lights } directional lights.')
 | 
						|
        else:
 | 
						|
            # show size for all types
 | 
						|
            colatlas_mixed = colatlas.column()
 | 
						|
            colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot')
 | 
						|
 | 
						|
            if rpdat.rp_shadowmap_atlas_max_size_spot != '':
 | 
						|
                atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_spot)
 | 
						|
                shadowmap_size = int(rpdat.rp_shadowmap_cascade)
 | 
						|
                spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot')
 | 
						|
 | 
						|
                if shadowmap_size > 2048:
 | 
						|
                    size_warning = True
 | 
						|
 | 
						|
                col = colatlas_mixed.row()
 | 
						|
                col.alignment = 'RIGHT'
 | 
						|
                col.label(text=f'Enough space for {spot_lights} spot lights.')
 | 
						|
 | 
						|
            colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point')
 | 
						|
 | 
						|
            if rpdat.rp_shadowmap_atlas_max_size_point != '':
 | 
						|
                atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_point)
 | 
						|
                shadowmap_size = int(rpdat.rp_shadowmap_cube)
 | 
						|
                point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point')
 | 
						|
 | 
						|
                if shadowmap_size > 2048:
 | 
						|
                    size_warning = True
 | 
						|
 | 
						|
                col = colatlas_mixed.row()
 | 
						|
                col.alignment = 'RIGHT'
 | 
						|
                col.label(text=f'Enough space for {point_lights} point lights.')
 | 
						|
 | 
						|
            colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun')
 | 
						|
 | 
						|
            if rpdat.rp_shadowmap_atlas_max_size_sun != '':
 | 
						|
                atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_sun)
 | 
						|
                shadowmap_size = int(rpdat.rp_shadowmap_cascade)
 | 
						|
                dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun')
 | 
						|
 | 
						|
                if shadowmap_size > 2048:
 | 
						|
                    size_warning = True
 | 
						|
 | 
						|
                col = colatlas_mixed.row()
 | 
						|
                col.alignment = 'RIGHT'
 | 
						|
                col.label(text=f'Enough space for {dir_lights} directional lights.')
 | 
						|
 | 
						|
        # show warning when user picks a size higher than 2048 (arbitrary number).
 | 
						|
        if size_warning:
 | 
						|
            col = layout.column()
 | 
						|
            row = col.row()
 | 
						|
            row.alignment = 'RIGHT'
 | 
						|
            row.label(text='Warning: Game will crash if texture size is higher than max texture size allowed by target.', icon='ERROR')
 | 
						|
 | 
						|
class LNX_PT_RenderPathVoxelsPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Voxels"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.prop(rpdat, 'rp_voxels')
 | 
						|
        col = layout.column()
 | 
						|
        col.enabled = rpdat.rp_voxels != 'Off'
 | 
						|
        col2 = col.column()
 | 
						|
        col2.enabled = rpdat.rp_voxels == 'Voxel GI'
 | 
						|
        col3 = col.column()
 | 
						|
        col3.enabled = rpdat.rp_voxels == 'Voxel AO'
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_shadows', text='Shadows')
 | 
						|
        col2.prop(rpdat, 'lnx_voxelgi_refract', text='Refraction')
 | 
						|
        #col.prop(rpdat, 'lnx_voxelgi_clipmap_count')
 | 
						|
        #col.prop(rpdat, 'lnx_voxelgi_cones')
 | 
						|
        col.prop(rpdat, 'rp_voxelgi_resolution')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_size')
 | 
						|
        #col.prop(rpdat, 'rp_voxelgi_resolution_z')
 | 
						|
        col2.enabled = rpdat.rp_voxels == 'Voxel GI'
 | 
						|
        #col.prop(rpdat, 'lnx_voxelgi_temporal')
 | 
						|
        col.label(text="Light")
 | 
						|
        col2 = col.column()
 | 
						|
        col2.enabled = rpdat.rp_voxels == 'Voxel GI'
 | 
						|
        col2.prop(rpdat, 'lnx_voxelgi_diff')
 | 
						|
        col2.prop(rpdat, 'lnx_voxelgi_spec')
 | 
						|
        col2.prop(rpdat, 'lnx_voxelgi_refr')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_shad')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_env')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_occ')
 | 
						|
        col.label(text="Ray")
 | 
						|
        #col.prop(rpdat, 'lnx_voxelgi_offset')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_step')
 | 
						|
        col.prop(rpdat, 'lnx_voxelgi_range')
 | 
						|
        #col.prop(rpdat, 'lnx_voxelgi_aperture')
 | 
						|
 | 
						|
class LNX_PT_RenderPathWorldPanel(bpy.types.Panel):
 | 
						|
    bl_label = "World"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.prop(rpdat, "rp_background")
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_irradiance')
 | 
						|
        colb = col.column()
 | 
						|
        colb.enabled = rpdat.lnx_irradiance
 | 
						|
        colb.prop(rpdat, 'lnx_radiance')
 | 
						|
        sub = colb.row()
 | 
						|
        sub.enabled = rpdat.lnx_radiance
 | 
						|
        sub.prop(rpdat, 'lnx_radiance_size')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        layout.prop(rpdat, 'lnx_clouds')
 | 
						|
 | 
						|
        col = layout.column(align=True)
 | 
						|
        col.prop(rpdat, "rp_water")
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.rp_water
 | 
						|
        col.prop(rpdat, 'lnx_water_level')
 | 
						|
        col.prop(rpdat, 'lnx_water_density')
 | 
						|
        col.prop(rpdat, 'lnx_water_displace')
 | 
						|
        col.prop(rpdat, 'lnx_water_speed')
 | 
						|
        col.prop(rpdat, 'lnx_water_freq')
 | 
						|
        col.prop(rpdat, 'lnx_water_refract')
 | 
						|
        col.prop(rpdat, 'lnx_water_reflect')
 | 
						|
        col.prop(rpdat, 'lnx_water_color')
 | 
						|
 | 
						|
class LNX_PT_RenderPathPostProcessPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Post Process"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        self.layout.prop(rpdat, "rp_render_to_texture", text="")
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.enabled = rpdat.rp_render_to_texture
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_antialiasing")
 | 
						|
        col.prop(rpdat, "rp_supersampling")
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_rp_resolution')
 | 
						|
        if rpdat.lnx_rp_resolution == 'Custom':
 | 
						|
            col.prop(rpdat, 'lnx_rp_resolution_size')
 | 
						|
            col.prop(rpdat, 'lnx_rp_resolution_filter')
 | 
						|
        col.prop(rpdat, 'rp_dynres')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_ssgi")
 | 
						|
        sub = col.column()
 | 
						|
        sub.enabled = rpdat.rp_ssgi != 'Off'
 | 
						|
        sub.prop(rpdat, 'lnx_ssgi_half_res')
 | 
						|
        #sub.prop(rpdat, 'lnx_ssgi_rays')
 | 
						|
        sub.prop(rpdat, 'lnx_ssgi_radius')
 | 
						|
        sub.prop(rpdat, 'lnx_ssgi_strength')
 | 
						|
        sub.prop(rpdat, 'lnx_ssgi_samples')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        row = layout.row()
 | 
						|
        row.enabled = rpdat.lnx_material_model == 'Full'
 | 
						|
        row.prop(rpdat, 'lnx_micro_shadowing')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_ssr")
 | 
						|
        col.prop(rpdat, 'lnx_ssr_half_res')
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.rp_ssr
 | 
						|
        col.prop(rpdat, 'lnx_ssr_ray_step')
 | 
						|
        col.prop(rpdat, 'lnx_ssr_search_dist')
 | 
						|
        col.prop(rpdat, 'lnx_ssr_falloff_exp')
 | 
						|
        col.prop(rpdat, 'lnx_ssr_jitter')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_ss_refraction")
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.rp_ss_refraction
 | 
						|
        col.prop(rpdat, 'lnx_ss_refraction_ray_step')
 | 
						|
        col.prop(rpdat, 'lnx_ss_refraction_search_dist')
 | 
						|
        col.prop(rpdat, 'lnx_ss_refraction_falloff_exp')
 | 
						|
        col.prop(rpdat, 'lnx_ss_refraction_jitter')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_ssrs')
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.lnx_ssrs
 | 
						|
        col.prop(rpdat, 'lnx_ssrs_ray_step')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_bloom")
 | 
						|
        _col = col.column()
 | 
						|
        _col.enabled = rpdat.rp_bloom
 | 
						|
        if bpy.app.version < (4, 3, 0):
 | 
						|
            _col.prop(rpdat, 'lnx_bloom_follow_blender')
 | 
						|
            if not rpdat.lnx_bloom_follow_blender:
 | 
						|
                _col.prop(rpdat, 'lnx_bloom_threshold')
 | 
						|
                _col.prop(rpdat, 'lnx_bloom_knee')
 | 
						|
                _col.prop(rpdat, 'lnx_bloom_radius')
 | 
						|
                _col.prop(rpdat, 'lnx_bloom_strength')
 | 
						|
        else:
 | 
						|
            _col.prop(rpdat, 'lnx_bloom_threshold')
 | 
						|
            _col.prop(rpdat, 'lnx_bloom_knee')
 | 
						|
            _col.prop(rpdat, 'lnx_bloom_radius')
 | 
						|
            _col.prop(rpdat, 'lnx_bloom_strength')
 | 
						|
        _col.prop(rpdat, 'lnx_bloom_quality')
 | 
						|
        _col.prop(rpdat, 'lnx_bloom_anti_flicker')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_motionblur")
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.rp_motionblur != 'Off'
 | 
						|
        col.prop(rpdat, 'lnx_motion_blur_intensity')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_volumetriclight")
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.rp_volumetriclight
 | 
						|
        col.prop(rpdat, 'lnx_volumetric_light_air_color')
 | 
						|
        col.prop(rpdat, 'lnx_volumetric_light_air_turbidity')
 | 
						|
        col.prop(rpdat, 'lnx_volumetric_light_steps')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_chromatic_aberration")
 | 
						|
        col = col.column()
 | 
						|
        col.enabled = rpdat.rp_chromatic_aberration
 | 
						|
        col.prop(rpdat, 'lnx_chromatic_aberration_type')
 | 
						|
        col.prop(rpdat, 'lnx_chromatic_aberration_strength')
 | 
						|
        if rpdat.lnx_chromatic_aberration_type == "Spectral":
 | 
						|
            col.prop(rpdat, 'lnx_chromatic_aberration_samples')
 | 
						|
 | 
						|
class LNX_PT_RenderPathCompositorPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Compositor"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "LNX_PT_RenderPathPanel"
 | 
						|
 | 
						|
    def draw_header(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
        self.layout.prop(rpdat, "rp_compositornodes", text="")
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        if len(wrd.lnx_rplist) <= wrd.lnx_rplist_index:
 | 
						|
            return
 | 
						|
        rpdat = wrd.lnx_rplist[wrd.lnx_rplist_index]
 | 
						|
 | 
						|
        layout.enabled = rpdat.rp_compositornodes
 | 
						|
        layout.prop(rpdat, 'lnx_tonemap')
 | 
						|
        layout.prop(rpdat, 'lnx_dithering')
 | 
						|
        if rpdat.lnx_dithering != 'Off':
 | 
						|
            row = layout.row(align=True) 
 | 
						|
            row.prop(rpdat, 'lnx_dithering_strength')
 | 
						|
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_letterbox')
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.lnx_letterbox
 | 
						|
        col.prop(rpdat, 'lnx_letterbox_color')
 | 
						|
        col.prop(rpdat, 'lnx_letterbox_size')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_sharpen')
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.lnx_sharpen
 | 
						|
        col.prop(rpdat, 'lnx_sharpen_color')
 | 
						|
        col.prop(rpdat, 'lnx_sharpen_size')
 | 
						|
        col.prop(rpdat, 'lnx_sharpen_strength')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        draw_conditional_prop(col, 'Distort', rpdat, 'lnx_distort', 'lnx_distort_strength')
 | 
						|
        draw_conditional_prop(col, 'Film Grain', rpdat, 'lnx_grain', 'lnx_grain_strength')
 | 
						|
        draw_conditional_prop(col, 'Vignette', rpdat, 'lnx_vignette', 'lnx_vignette_strength')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_fog')
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.lnx_fog
 | 
						|
        col.prop(rpdat, 'lnx_fog_color')
 | 
						|
        col.prop(rpdat, 'lnx_fog_amounta')
 | 
						|
        col.prop(rpdat, 'lnx_fog_amountb')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, "rp_autoexposure")
 | 
						|
        sub = col.column(align=True)
 | 
						|
        sub.enabled = rpdat.rp_autoexposure
 | 
						|
        sub.prop(rpdat, 'lnx_autoexposure_strength', text='Strength')
 | 
						|
        sub.prop(rpdat, 'lnx_autoexposure_speed', text='Speed')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_fisheye')
 | 
						|
        col.prop(rpdat, 'lnx_lensflare')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_lens')
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.lnx_lens
 | 
						|
        col.prop(rpdat, 'lnx_lens_texture')
 | 
						|
        if rpdat.lnx_lens_texture != "":
 | 
						|
            col.prop(rpdat, 'lnx_lens_texture_masking')
 | 
						|
            if rpdat.lnx_lens_texture_masking:
 | 
						|
                sub = col.column(align=True)
 | 
						|
                sub.prop(rpdat, 'lnx_lens_texture_masking_centerMinClip')
 | 
						|
                sub.prop(rpdat, 'lnx_lens_texture_masking_centerMaxClip')
 | 
						|
                sub = col.column(align=True)
 | 
						|
                sub.prop(rpdat, 'lnx_lens_texture_masking_luminanceMin')
 | 
						|
                sub.prop(rpdat, 'lnx_lens_texture_masking_luminanceMax')
 | 
						|
                col.prop(rpdat, 'lnx_lens_texture_masking_brightnessExp')
 | 
						|
                layout.separator()
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        col = layout.column()
 | 
						|
        col.prop(rpdat, 'lnx_lut')
 | 
						|
        col = col.column(align=True)
 | 
						|
        col.enabled = rpdat.lnx_lut
 | 
						|
        col.prop(rpdat, 'lnx_lut_texture')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
class LNX_PT_BakePanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Bake"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "render"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        scn = bpy.data.scenes[context.scene.name]
 | 
						|
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.prop(scn, "lnx_bakemode", expand=True)
 | 
						|
 | 
						|
        if scn.lnx_bakemode == "Static Map":
 | 
						|
 | 
						|
            row = layout.row(align=True)
 | 
						|
            row.alignment = 'EXPAND'
 | 
						|
            row.operator("lnx.bake_textures", icon="RENDER_STILL")
 | 
						|
            row.operator("lnx.bake_apply")
 | 
						|
 | 
						|
            col = layout.column()
 | 
						|
            col.prop(scn, 'lnx_bakelist_scale')
 | 
						|
            col.prop(scn.cycles, "samples")
 | 
						|
 | 
						|
            layout.prop(scn, 'lnx_bakelist_unwrap')
 | 
						|
 | 
						|
            rows = 2
 | 
						|
            if len(scn.lnx_bakelist) > 1:
 | 
						|
                rows = 4
 | 
						|
            row = layout.row()
 | 
						|
            row.template_list("LNX_UL_BakeList", "The_List", scn, "lnx_bakelist", scn, "lnx_bakelist_index", rows=rows)
 | 
						|
            col = row.column(align=True)
 | 
						|
            col.operator("lnx_bakelist.new_item", icon='ADD', text="")
 | 
						|
            col.operator("lnx_bakelist.delete_item", icon='REMOVE', text="")
 | 
						|
            col.menu("LNX_MT_BakeListSpecials", icon='DOWNARROW_HLT', text="")
 | 
						|
 | 
						|
            if len(scn.lnx_bakelist) > 1:
 | 
						|
                col.separator()
 | 
						|
                op = col.operator("lnx_bakelist.move_item", icon='TRIA_UP', text="")
 | 
						|
                op.direction = 'UP'
 | 
						|
                op = col.operator("lnx_bakelist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
                op.direction = 'DOWN'
 | 
						|
 | 
						|
            if scn.lnx_bakelist_index >= 0 and len(scn.lnx_bakelist) > 0:
 | 
						|
                item = scn.lnx_bakelist[scn.lnx_bakelist_index]
 | 
						|
                layout.prop_search(item, "obj", bpy.data, "objects", text="Object")
 | 
						|
                layout.prop(item, "res_x")
 | 
						|
                layout.prop(item, "res_y")
 | 
						|
 | 
						|
class LnxGenLodButton(bpy.types.Operator):
 | 
						|
    """Automatically generate LoD levels."""
 | 
						|
    bl_idname = 'lnx.generate_lod'
 | 
						|
    bl_label = 'Auto Generate'
 | 
						|
 | 
						|
    def lod_name(self, name, level):
 | 
						|
        return name + '_LOD' + str(level + 1)
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        obj = context.object
 | 
						|
        if obj == None:
 | 
						|
            return{'CANCELLED'}
 | 
						|
 | 
						|
        # Clear
 | 
						|
        mdata = context.object.data
 | 
						|
        mdata.lnx_lodlist_index = 0
 | 
						|
        mdata.lnx_lodlist.clear()
 | 
						|
 | 
						|
        # Lod levels
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        ratio = wrd.lnx_lod_gen_ratio
 | 
						|
        num_levels = wrd.lnx_lod_gen_levels
 | 
						|
        for level in range(0, num_levels):
 | 
						|
            new_obj = obj.copy()
 | 
						|
            for i in range(0, 3):
 | 
						|
                new_obj.location[i] = 0
 | 
						|
                new_obj.rotation_euler[i] = 0
 | 
						|
                new_obj.scale[i] = 1
 | 
						|
            new_obj.data = obj.data.copy()
 | 
						|
            new_obj.name = self.lod_name(obj.name, level)
 | 
						|
            new_obj.parent = obj
 | 
						|
            new_obj.hide_viewport = True
 | 
						|
            new_obj.hide_render = True
 | 
						|
            mod = new_obj.modifiers.new('Decimate', 'DECIMATE')
 | 
						|
            mod.ratio = ratio
 | 
						|
            ratio *= wrd.lnx_lod_gen_ratio
 | 
						|
            context.scene.collection.objects.link(new_obj)
 | 
						|
 | 
						|
        # Screen sizes
 | 
						|
        for level in range(0, num_levels):
 | 
						|
            mdata.lnx_lodlist.add()
 | 
						|
            mdata.lnx_lodlist[-1].name = self.lod_name(obj.name, level)
 | 
						|
            mdata.lnx_lodlist[-1].screen_size_prop = (1 - (1 / (num_levels + 1)) * level) - (1 / (num_levels + 1))
 | 
						|
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_PT_LodPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Lod"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "object"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        obj = bpy.context.object
 | 
						|
 | 
						|
        # Mesh only for now
 | 
						|
        if obj.type != 'MESH':
 | 
						|
            return
 | 
						|
 | 
						|
        mdata = obj.data
 | 
						|
 | 
						|
        rows = 2
 | 
						|
        if len(mdata.lnx_lodlist) > 1:
 | 
						|
            rows = 4
 | 
						|
 | 
						|
        row = layout.row()
 | 
						|
        row.template_list("LNX_UL_LodList", "The_List", mdata, "lnx_lodlist", mdata, "lnx_lodlist_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_lodlist.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_lodlist.delete_item", icon='REMOVE', text="")
 | 
						|
 | 
						|
        if len(mdata.lnx_lodlist) > 1:
 | 
						|
            col.separator()
 | 
						|
            op = col.operator("lnx_lodlist.move_item", icon='TRIA_UP', text="")
 | 
						|
            op.direction = 'UP'
 | 
						|
            op = col.operator("lnx_lodlist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
            op.direction = 'DOWN'
 | 
						|
 | 
						|
        if mdata.lnx_lodlist_index >= 0 and len(mdata.lnx_lodlist) > 0:
 | 
						|
            item = mdata.lnx_lodlist[mdata.lnx_lodlist_index]
 | 
						|
            layout.prop_search(item, "name", bpy.data, "objects", text="Object")
 | 
						|
            layout.prop(item, "screen_size_prop")
 | 
						|
        layout.prop(mdata, "lnx_lod_material")
 | 
						|
 | 
						|
        # Auto lod for meshes
 | 
						|
        if obj.type == 'MESH':
 | 
						|
            layout.separator()
 | 
						|
            layout.operator("lnx.generate_lod")
 | 
						|
            wrd = bpy.data.worlds['Lnx']
 | 
						|
            layout.prop(wrd, 'lnx_lod_gen_levels')
 | 
						|
            layout.prop(wrd, 'lnx_lod_gen_ratio')
 | 
						|
 | 
						|
class LnxGenTerrainButton(bpy.types.Operator):
 | 
						|
    '''Generate terrain sectors'''
 | 
						|
    bl_idname = 'lnx.generate_terrain'
 | 
						|
    bl_label = 'Generate'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        scn = context.scene
 | 
						|
        if scn == None:
 | 
						|
            return{'CANCELLED'}
 | 
						|
        sectors = scn.lnx_terrain_sectors
 | 
						|
        size = scn.lnx_terrain_sector_size
 | 
						|
        height_scale = scn.lnx_terrain_height_scale
 | 
						|
 | 
						|
        # Create material
 | 
						|
        mat = bpy.data.materials.new(name="Terrain")
 | 
						|
        mat.use_nodes = True
 | 
						|
        nodes = mat.node_tree.nodes
 | 
						|
        links = mat.node_tree.links
 | 
						|
        node = nodes.new('ShaderNodeDisplacement')
 | 
						|
        node.location = (-200, 100)
 | 
						|
        node.inputs[2].default_value = height_scale
 | 
						|
        node.space = 'WORLD'
 | 
						|
        links.new(nodes['Material Output'].inputs[2], node.outputs[0])
 | 
						|
        node = nodes.new('ShaderNodeTexImage')
 | 
						|
        node.location = (-600, 100)
 | 
						|
        node.interpolation = 'Closest'
 | 
						|
        node.extension = 'EXTEND'
 | 
						|
        node.lnx_material_param = True
 | 
						|
        node.name = '_TerrainHeight'
 | 
						|
        node.label = '_TerrainHeight' # Height-map texture link for this sector
 | 
						|
        links.new(nodes['Displacement'].inputs[0], nodes['_TerrainHeight'].outputs[0])
 | 
						|
        node = nodes.new('ShaderNodeBump')
 | 
						|
        node.location = (-200, -200)
 | 
						|
        node.inputs[0].default_value = 5.0
 | 
						|
        links.new(nodes['Bump'].inputs[2], nodes['_TerrainHeight'].outputs[0])
 | 
						|
        links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0])
 | 
						|
 | 
						|
        # Create sectors
 | 
						|
        root_obj = bpy.data.objects.new("Terrain", None)
 | 
						|
        root_obj.location[0] = 0
 | 
						|
        root_obj.location[1] = 0
 | 
						|
        root_obj.location[2] = 0
 | 
						|
        root_obj.lnx_export = False
 | 
						|
        scn.collection.objects.link(root_obj)
 | 
						|
        scn.lnx_terrain_object = root_obj
 | 
						|
 | 
						|
        for i in range(sectors[0] * sectors[1]):
 | 
						|
            j = str(i + 1).zfill(2)
 | 
						|
            x = i % sectors[0]
 | 
						|
            y = int(i / sectors[0])
 | 
						|
            bpy.ops.mesh.primitive_plane_add(location=(x * size, -y * size, 0))
 | 
						|
            slice_obj = bpy.context.active_object
 | 
						|
            slice_obj.scale[0] = size / 2
 | 
						|
            slice_obj.scale[1] = -(size / 2)
 | 
						|
            slice_obj.scale[2] = height_scale
 | 
						|
            slice_obj.data.materials.append(mat)
 | 
						|
            for p in slice_obj.data.polygons:
 | 
						|
                p.use_smooth = True
 | 
						|
            slice_obj.name = 'Terrain.' + j
 | 
						|
            slice_obj.parent = root_obj
 | 
						|
            sub_mod = slice_obj.modifiers.new('Subdivision', 'SUBSURF')
 | 
						|
            sub_mod.subdivision_type = 'SIMPLE'
 | 
						|
            disp_mod = slice_obj.modifiers.new('Displace', 'DISPLACE')
 | 
						|
            disp_mod.texture_coords = 'UV'
 | 
						|
            disp_mod.texture = bpy.data.textures.new(name='Terrain.' + j, type='IMAGE')
 | 
						|
            disp_mod.texture.extension = 'EXTEND'
 | 
						|
            disp_mod.texture.use_interpolation = False
 | 
						|
            disp_mod.texture.use_mipmap = False
 | 
						|
            disp_mod.texture.image = bpy.data.images.load(filepath=scn.lnx_terrain_textures+'/heightmap_' + j + '.png')
 | 
						|
            f = 1
 | 
						|
            levels = 0
 | 
						|
            while f < disp_mod.texture.image.size[0]:
 | 
						|
                f *= 2
 | 
						|
                levels += 1
 | 
						|
            sub_mod.levels = sub_mod.render_levels = levels
 | 
						|
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_PT_TerrainPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Terrain"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "scene"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        scn = bpy.context.scene
 | 
						|
        if scn == None:
 | 
						|
            return
 | 
						|
        layout.prop(scn, 'lnx_terrain_textures')
 | 
						|
        layout.prop(scn, 'lnx_terrain_sectors')
 | 
						|
        layout.prop(scn, 'lnx_terrain_sector_size')
 | 
						|
        layout.prop(scn, 'lnx_terrain_height_scale')
 | 
						|
        layout.operator('lnx.generate_terrain')
 | 
						|
        layout.prop(scn, 'lnx_terrain_object')
 | 
						|
 | 
						|
class LNX_PT_TilesheetPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Tilesheet"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "material"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        rows = 2
 | 
						|
        if len(wrd.lnx_tilesheetlist) > 1:
 | 
						|
            rows = 4
 | 
						|
        row = layout.row()
 | 
						|
        row.template_list("LNX_UL_TilesheetList", "The_List", wrd, "lnx_tilesheetlist", wrd, "lnx_tilesheetlist_index", rows=rows)
 | 
						|
        col = row.column(align=True)
 | 
						|
        col.operator("lnx_tilesheetlist.new_item", icon='ADD', text="")
 | 
						|
        col.operator("lnx_tilesheetlist.delete_item", icon='REMOVE', text="")
 | 
						|
 | 
						|
        if len(wrd.lnx_tilesheetlist) > 1:
 | 
						|
            col.separator()
 | 
						|
            op = col.operator("lnx_tilesheetlist.move_item", icon='TRIA_UP', text="")
 | 
						|
            op.direction = 'UP'
 | 
						|
            op = col.operator("lnx_tilesheetlist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
            op.direction = 'DOWN'
 | 
						|
 | 
						|
        if wrd.lnx_tilesheetlist_index >= 0 and len(wrd.lnx_tilesheetlist) > 0:
 | 
						|
            dat = wrd.lnx_tilesheetlist[wrd.lnx_tilesheetlist_index]
 | 
						|
            layout.prop(dat, "tilesx_prop")
 | 
						|
            layout.prop(dat, "tilesy_prop")
 | 
						|
            layout.prop(dat, "framerate_prop")
 | 
						|
 | 
						|
            layout.label(text='Actions')
 | 
						|
            rows = 2
 | 
						|
            if len(dat.lnx_tilesheetactionlist) > 1:
 | 
						|
                rows = 4
 | 
						|
            row = layout.row()
 | 
						|
            row.template_list("LNX_UL_TilesheetList", "The_List", dat, "lnx_tilesheetactionlist", dat, "lnx_tilesheetactionlist_index", rows=rows)
 | 
						|
            col = row.column(align=True)
 | 
						|
            col.operator("lnx_tilesheetactionlist.new_item", icon='ADD', text="")
 | 
						|
            col.operator("lnx_tilesheetactionlist.delete_item", icon='REMOVE', text="")
 | 
						|
 | 
						|
            if len(dat.lnx_tilesheetactionlist) > 1:
 | 
						|
                col.separator()
 | 
						|
                op = col.operator("lnx_tilesheetactionlist.move_item", icon='TRIA_UP', text="")
 | 
						|
                op.direction = 'UP'
 | 
						|
                op = col.operator("lnx_tilesheetactionlist.move_item", icon='TRIA_DOWN', text="")
 | 
						|
                op.direction = 'DOWN'
 | 
						|
 | 
						|
            if dat.lnx_tilesheetactionlist_index >= 0 and len(dat.lnx_tilesheetactionlist) > 0:
 | 
						|
                adat = dat.lnx_tilesheetactionlist[dat.lnx_tilesheetactionlist_index]
 | 
						|
                layout.prop(adat, "start_prop")
 | 
						|
                layout.prop(adat, "end_prop")
 | 
						|
                layout.prop(adat, "loop_prop")
 | 
						|
 | 
						|
class LnxPrintTraitsButton(bpy.types.Operator):
 | 
						|
    bl_idname = 'lnx.print_traits'
 | 
						|
    bl_label = 'Print All Traits'
 | 
						|
    bl_description = 'Returns all traits in current blend'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        for s in bpy.data.scenes:
 | 
						|
            print('Scene: {0}'.format(s.name))
 | 
						|
            for t in s.lnx_traitlist:
 | 
						|
                if not t.enabled_prop:
 | 
						|
                    continue
 | 
						|
                tname = "undefined"
 | 
						|
                if t.type_prop == 'Haxe Script' or "Bundled":
 | 
						|
                    tname = t.class_name_prop
 | 
						|
                if t.type_prop == 'Logic Nodes':
 | 
						|
                    tname = t.node_tree_prop.name
 | 
						|
                if t.type_prop == 'UI Canvas':
 | 
						|
                    tname = t.canvas_name_prop
 | 
						|
                if t.type_prop == 'WebAssembly':
 | 
						|
                    tname = t.webassembly_prop
 | 
						|
                print('Scene Trait: {0} ("{1}")'.format(s.name, tname))
 | 
						|
            for o in s.objects:
 | 
						|
                for t in o.lnx_traitlist:
 | 
						|
                    if not t.enabled_prop:
 | 
						|
                        continue
 | 
						|
                    tname = "undefined"
 | 
						|
                    if t.type_prop == 'Haxe Script' or "Bundled":
 | 
						|
                        tname = t.class_name_prop
 | 
						|
                    if t.type_prop == 'Logic Nodes':
 | 
						|
                        tname = t.node_tree_prop.name
 | 
						|
                    if t.type_prop == 'UI Canvas':
 | 
						|
                        tname = t.canvas_name_prop
 | 
						|
                    if t.type_prop == 'WebAssembly':
 | 
						|
                        tname = t.webassembly_prop
 | 
						|
                    print(' Object Trait: {0} ("{1}")'.format(o.name, tname))
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LNX_PT_MaterialNodePanel(bpy.types.Panel):
 | 
						|
    bl_label = 'Leenkx Material Node'
 | 
						|
    bl_idname = 'LNX_PT_MaterialNodePanel'
 | 
						|
    bl_space_type = 'NODE_EDITOR'
 | 
						|
    bl_region_type = 'UI'
 | 
						|
    bl_category = 'Leenkx'
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        return (context.space_data.tree_type == 'ShaderNodeTree'
 | 
						|
                and context.space_data.edit_tree
 | 
						|
                and context.space_data.shader_type == 'OBJECT')
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        n = context.active_node
 | 
						|
        if n != None and (n.bl_idname == 'ShaderNodeRGB' or n.bl_idname == 'ShaderNodeValue' or n.bl_idname == 'ShaderNodeTexImage'):
 | 
						|
            layout.prop(context.active_node, 'lnx_material_param')
 | 
						|
 | 
						|
class LNX_OT_CopyToBundled(bpy.types.Operator):
 | 
						|
    bl_label = 'Copy To Bundled'
 | 
						|
    bl_idname = 'lnx.copy_to_bundled'
 | 
						|
    bl_description = ('Copies and repaths external image assets to project Bundled folder')
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        self.copy_to_bundled(bpy.data.images)
 | 
						|
        return {'FINISHED'}
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def copy_to_bundled(self, data):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        project_path = lnx.utils.get_fp()
 | 
						|
 | 
						|
        # Blend - Images
 | 
						|
        for asset in data:
 | 
						|
            # File is saved
 | 
						|
            if asset.filepath_from_user() != '':
 | 
						|
                bundled_filepath = project_path + '/Bundled/' + asset.name
 | 
						|
                try:
 | 
						|
                    # Exists -> Yes
 | 
						|
                    if os.path.isfile(bundled_filepath):
 | 
						|
                        # Override -> Yes
 | 
						|
                        if (wrd.lnx_copy_override):
 | 
						|
                            # Valid file
 | 
						|
                            if asset.has_data:
 | 
						|
                                asset.filepath_raw = bundled_filepath
 | 
						|
                                asset.save()
 | 
						|
                                asset.reload()
 | 
						|
                                # Syntax - Yellow
 | 
						|
                                print(log.colorize(f'Asset name "{asset.name}" already exists, overriding the original', 33), file=sys.stderr)
 | 
						|
                            # Invalid file or corrupted
 | 
						|
                            else:
 | 
						|
                                # Syntax - Red
 | 
						|
                                log.error(f'Asset name "{asset.name}" has no data to save or copy, skipping')
 | 
						|
                                continue
 | 
						|
                        # Override -> No
 | 
						|
                        else:
 | 
						|
                            # Syntax - Yellow
 | 
						|
                            print(log.colorize(f'Asset name "{asset.name}" already exists, skipping', 33), file=sys.stderr)
 | 
						|
                            continue
 | 
						|
                    # Exists -> No
 | 
						|
                    else:
 | 
						|
                        # Valid file
 | 
						|
                        if asset.has_data:
 | 
						|
                            asset.filepath_raw = bundled_filepath
 | 
						|
                            asset.save()
 | 
						|
                            asset.reload()
 | 
						|
                            # Syntax - Green
 | 
						|
                            print(log.colorize(f'Asset name "{asset.name}" was successfully copied', 32), file=sys.stderr)
 | 
						|
                        # Invalid file or corrupted
 | 
						|
                        else:
 | 
						|
                            # Syntax - Red
 | 
						|
                            log.error(f'Asset name "{asset.name}" has no data to save or copy, skipping')
 | 
						|
                            continue
 | 
						|
                except:
 | 
						|
                    # Syntax - Red
 | 
						|
                    log.error(f'Insufficient write permissions or other issues occurred')
 | 
						|
                    continue
 | 
						|
            # File is unsaved
 | 
						|
            else:
 | 
						|
                # Syntax - Purple
 | 
						|
                log.warn(f'Asset name "{asset.name}" is either packed or unsaved, skipping')
 | 
						|
                continue
 | 
						|
 | 
						|
class LNX_OT_ShowFileVersionInfo(bpy.types.Operator):
 | 
						|
    bl_label = 'Show old file version info'
 | 
						|
    bl_idname = 'lnx.show_old_file_version_info'
 | 
						|
    bl_description = ('Displays an info panel that warns about opening a file'
 | 
						|
                       'which was created in a previous version of Leenkx')
 | 
						|
    bl_options = {'INTERNAL'}
 | 
						|
 | 
						|
    wrd = None
 | 
						|
 | 
						|
    def draw_message_box(self, context):
 | 
						|
        file_version = LNX_OT_ShowFileVersionInfo.wrd.lnx_version
 | 
						|
        current_version = props.lnx_version
 | 
						|
 | 
						|
 | 
						|
        layout = self.layout
 | 
						|
        layout = layout.column(align=True)
 | 
						|
        layout.alignment = 'EXPAND'
 | 
						|
 | 
						|
        if current_version == file_version:
 | 
						|
            layout.label('This file was saved in', icon='INFO')
 | 
						|
            layout.label('the current Leenkx version', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
            layout.label(f'(version: {current_version}')
 | 
						|
            row = layout.row(align=True)
 | 
						|
            row.active_default = True
 | 
						|
            row.operator('lnx.discard_popup', text='Ok')
 | 
						|
 | 
						|
        # this will help order versions better, somewhat.
 | 
						|
        # note: this is NOT complete
 | 
						|
        current_version = tuple( current_version.split('.') )
 | 
						|
        file_version = tuple( file_version.split('.') )
 | 
						|
 | 
						|
        if current_version > file_version:
 | 
						|
            layout.label(text='Warning: This file was saved in a', icon='ERROR')
 | 
						|
            layout.label(text='previous version of Leenkx!', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
 | 
						|
            layout.label(text='Please inform yourself about breaking changes!', icon='BLANK1')
 | 
						|
            layout.label(text=f'File saved in: {file_version}', icon='BLANK1')
 | 
						|
            layout.label(text=f'Current version: {current_version}', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
            layout.separator()
 | 
						|
            layout.label(text='Should Leenkx try to automatically update', icon='BLANK1')
 | 
						|
            layout.label(text='the file to the current SDK version?', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
 | 
						|
            row = layout.row(align=True)
 | 
						|
            row.active_default = True
 | 
						|
            row.operator('lnx.update_file_sdk', text='Yes')
 | 
						|
            row.active_default = False
 | 
						|
            row.operator('lnx.discard_popup', text='No')
 | 
						|
        else:
 | 
						|
            layout.label(text='Warning: This file was saved in a', icon='ERROR')
 | 
						|
            layout.label(text='future version of Leenkx!', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
 | 
						|
            layout.label(text='It is impossible to downgrade a file,', icon='BLANK1')
 | 
						|
            layout.label(text='Something will probably be broken here.', icon='BLANK1')
 | 
						|
            layout.label(text=f'File saved in: {file_version}', icon='BLANK1')
 | 
						|
            layout.label(text=f'Current version: {current_version}', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
            layout.separator()
 | 
						|
            layout.label(text='Please check how this file was created', icon='BLANK1')
 | 
						|
            layout.separator()
 | 
						|
 | 
						|
            row = layout.row(align=True)
 | 
						|
            row.active_default = True
 | 
						|
            row.operator('lnx.discard_popup', text='Ok')
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        LNX_OT_ShowFileVersionInfo.wrd = bpy.data.worlds['Lnx']
 | 
						|
        context.window_manager.popover(LNX_OT_ShowFileVersionInfo.draw_message_box, ui_units_x=16)
 | 
						|
 | 
						|
        return {"FINISHED"}
 | 
						|
 | 
						|
class LNX_OT_ShowNodeUpdateErrors(bpy.types.Operator):
 | 
						|
    bl_label = 'Show upgrade failure details'
 | 
						|
    bl_idname = 'lnx.show_node_update_errors'
 | 
						|
    bl_description = ('Displays an info panel that shows the different errors that occurred when upgrading nodes')
 | 
						|
 | 
						|
    wrd = None  # a helper internal variable
 | 
						|
 | 
						|
    def draw_message_box(self, context):
 | 
						|
        list_of_errors = lnx.logicnode.replacement.replacement_errors.copy()
 | 
						|
        # note: list_of_errors is a set of tuples: `(error_type, node_class, tree_name)`
 | 
						|
        # where `error_type` can be "unregistered", "update failed", "future version", "bad version", or "misc."
 | 
						|
 | 
						|
        file_version = LNX_OT_ShowNodeUpdateErrors.wrd.lnx_version
 | 
						|
        current_version = props.lnx_version
 | 
						|
 | 
						|
        # this will help order versions better, somewhat.
 | 
						|
        # note: this is NOT complete
 | 
						|
        current_version_2 = tuple(current_version.split('.'))
 | 
						|
        file_version_2 = tuple(file_version.split('.'))
 | 
						|
        is_leenkx_upgrade = (current_version_2 > file_version_2)
 | 
						|
 | 
						|
        error_types = set()
 | 
						|
        errored_trees = set()
 | 
						|
        errored_nodes = set()
 | 
						|
        for error_entry in list_of_errors:
 | 
						|
            error_types.add(error_entry[0])
 | 
						|
            errored_nodes.add(error_entry[1])
 | 
						|
            errored_trees.add(error_entry[2])
 | 
						|
 | 
						|
        layout = self.layout
 | 
						|
        layout = layout.column(align=True)
 | 
						|
        layout.alignment = 'EXPAND'
 | 
						|
 | 
						|
        layout.label(text="Some nodes failed to be updated to the current Leenkx version", icon="ERROR")
 | 
						|
        if current_version == file_version:
 | 
						|
            layout.label(text="(This might be because you are using a development snapshot, or a homemade version ;) )", icon='BLANK1')
 | 
						|
        elif not is_leenkx_upgrade:
 | 
						|
            layout.label(text="(Please note that it is not possible do downgrade nodes to a previous version either.", icon='BLANK1')
 | 
						|
            layout.label(text="This might be the cause of your problem.)", icon='BLANK1')
 | 
						|
 | 
						|
        layout.label(text=f'File saved in: {file_version}', icon='BLANK1')
 | 
						|
        layout.label(text=f'Current version: {current_version}', icon='BLANK1')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        if 'update failed' in error_types:
 | 
						|
            layout.label(text="Some nodes do not have an update procedure to deal with the version saved in this file.", icon='BLANK1')
 | 
						|
            if current_version == file_version:
 | 
						|
                layout.label(text="(if you are a developer, this might be because you didn't implement it yet.)", icon='BLANK1')
 | 
						|
        if 'bad version' in error_types:
 | 
						|
            layout.label(text="Some nodes do not have version information attached to them.", icon='BLANK1')
 | 
						|
        if 'unregistered' in error_types:
 | 
						|
            if is_leenkx_upgrade:
 | 
						|
                layout.label(text='Some nodes seem to be too old to be understood by leenkx anymore', icon='BLANK1')
 | 
						|
            else:
 | 
						|
                layout.label(text="Some nodes are unknown to leenkx, either because they are too new or too old.", icon='BLANK1')
 | 
						|
        if 'future version' in error_types:
 | 
						|
            if is_leenkx_upgrade:
 | 
						|
                layout.label(text='Somehow, some nodes seem to have been created with a future version of leenkx.', icon='BLANK1')
 | 
						|
            else:
 | 
						|
                layout.label(text='Some nodes seem to have been created with a future version of leenkx.', icon='BLANK1')
 | 
						|
        if 'misc.' in error_types:
 | 
						|
            layout.label(text="Some nodes' update procedure failed to complete")
 | 
						|
 | 
						|
        layout.separator()
 | 
						|
        layout.label(text='the nodes impacted are the following:', icon='BLANK1')
 | 
						|
        for node in errored_nodes:
 | 
						|
            layout.label(text=f'   {node}', icon='BLANK1')
 | 
						|
        layout.separator()
 | 
						|
        layout.label(text='the node trees impacted are the following:', icon='BLANK1')
 | 
						|
        for tree in errored_trees:
 | 
						|
            layout.label(text=f'   "{tree}"', icon='BLANK1')
 | 
						|
 | 
						|
        layout.separator()
 | 
						|
        layout.label(text="A detailed error report has been saved next to the blender file.", icon='BLANK1')
 | 
						|
        layout.label(text="the file name is \"node_update_failure\", followed by the current time.", icon='BLANK1')
 | 
						|
        layout.separator()
 | 
						|
 | 
						|
        row = layout.row(align=True)
 | 
						|
        row.active_default = False
 | 
						|
        row.operator('lnx.discard_popup', text='Ok')
 | 
						|
        row.operator('lnx.open_project_folder', text='Open Project Folder', icon="FILE_FOLDER")
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        LNX_OT_ShowNodeUpdateErrors.wrd = bpy.data.worlds['Lnx']
 | 
						|
        context.window_manager.popover(LNX_OT_ShowNodeUpdateErrors.draw_message_box, ui_units_x=32)
 | 
						|
        return {"FINISHED"}
 | 
						|
 | 
						|
class LNX_OT_UpdateFileSDK(bpy.types.Operator):
 | 
						|
    bl_idname = 'lnx.update_file_sdk'
 | 
						|
    bl_label = 'Update file to current SDK version'
 | 
						|
    bl_description = bl_label
 | 
						|
    bl_options = {'INTERNAL'}
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
        # This allows for seamless migration from earlier versions of Leenkx
 | 
						|
        for rp in wrd.lnx_rplist: # TODO: deprecated
 | 
						|
            if rp.rp_gi != 'Off':
 | 
						|
                rp.rp_gi = 'Off'
 | 
						|
                rp.rp_voxelao = True
 | 
						|
 | 
						|
        # Replace deprecated nodes
 | 
						|
        lnx.logicnode.replacement.replace_all()
 | 
						|
 | 
						|
        wrd.lnx_version = props.lnx_version
 | 
						|
        wrd.lnx_commit = props.lnx_commit
 | 
						|
 | 
						|
        lnx.make.clean()
 | 
						|
        print(f'Project updated to SDK {props.lnx_version}. Please save the .blend file.')
 | 
						|
 | 
						|
        return {'FINISHED'}
 | 
						|
 | 
						|
class LNX_OT_DiscardPopup(bpy.types.Operator):
 | 
						|
    """Empty operator for discarding dialogs."""
 | 
						|
    bl_idname = 'lnx.discard_popup'
 | 
						|
    bl_label = 'OK'
 | 
						|
    bl_description = 'Discard'
 | 
						|
    bl_options = {'INTERNAL'}
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        return {'FINISHED'}
 | 
						|
 | 
						|
class LeenkxUpdateListAndroidEmulatorButton(bpy.types.Operator):
 | 
						|
    '''Updating the list of emulators for the Android platform'''
 | 
						|
    bl_idname = 'lnx.update_list_android_emulator'
 | 
						|
    bl_label = 'Update List Emulators'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if not lnx.utils.check_sdkpath(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if len(lnx.utils.get_android_sdk_root_path()) == 0:
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        os.environ['ANDROID_SDK_ROOT'] = lnx.utils.get_android_sdk_root_path()
 | 
						|
        items, err = lnx.utils.get_android_emulators_list()
 | 
						|
        if len(err) > 0:
 | 
						|
            print('Update List Emulators Warning: File "'+ lnx.utils.get_android_emulator_file() +'" not found. Check that the variable ANDROID_SDK_ROOT is correct in environment variables or in "Android SDK Path" setting: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path", then repeat operation "Publish"')
 | 
						|
            return{'FINISHED'}
 | 
						|
        if len(items) > 0:
 | 
						|
            items_enum = []
 | 
						|
            for i in items:
 | 
						|
                items_enum.append((i, i, i))
 | 
						|
            bpy.types.World.lnx_project_android_list_avd = EnumProperty(items=items_enum, name="Emulator", update=assets.invalidate_compiler_cache)
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
class LeenkxUpdateListAndroidEmulatorRunButton(bpy.types.Operator):
 | 
						|
    '''Launch Android emulator selected from the list'''
 | 
						|
    bl_idname = 'lnx.run_android_emulator'
 | 
						|
    bl_label = 'Launch Emulator'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.check_saved(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if not lnx.utils.check_sdkpath(self):
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        if len(lnx.utils.get_android_sdk_root_path()) == 0:
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        make.run_android_emulators(lnx.utils.get_android_emulator_name())
 | 
						|
        return{'FINISHED'}
 | 
						|
 | 
						|
 | 
						|
class LeenkxUpdateListInstalledVSButton(bpy.types.Operator):
 | 
						|
    """Update the list of installed Visual Studio versions for the Windows platform"""
 | 
						|
    bl_idname = 'lnx.update_list_installed_vs'
 | 
						|
    bl_label = '(Re-)Fetch Installed Visual Studio Versions'
 | 
						|
 | 
						|
    def execute(self, context):
 | 
						|
        if not lnx.utils.get_os_is_windows():
 | 
						|
            return {"CANCELLED"}
 | 
						|
 | 
						|
        success = lnx.utils_vs.fetch_installed_vs()
 | 
						|
        if not success:
 | 
						|
            self.report({"ERROR"}, "Could not fetch installed Visual Studio versions, check console for details.")
 | 
						|
            return {'CANCELLED'}
 | 
						|
 | 
						|
        return {'FINISHED'}
 | 
						|
 | 
						|
class LNX_PT_PhysicsProps(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Props"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "scene"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "SCENE_PT_rigid_body_world"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        return context.scene.rigidbody_world is not None
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        if wrd.lnx_physics_engine != 'Bullet' and wrd.lnx_physics_engine != 'Oimo':
 | 
						|
            row = layout.row()
 | 
						|
            row.alert = True
 | 
						|
            row.label(text="Physics debug drawing is only supported for the Bullet and Oimo physics engines")
 | 
						|
 | 
						|
        col = layout.column(align=False)
 | 
						|
        col.prop(wrd, "lnx_physics_fixed_step")
 | 
						|
 | 
						|
class LNX_PT_PhysicsDebugDrawingPanel(bpy.types.Panel):
 | 
						|
    bl_label = "Leenkx Debug Drawing"
 | 
						|
    bl_space_type = "PROPERTIES"
 | 
						|
    bl_region_type = "WINDOW"
 | 
						|
    bl_context = "scene"
 | 
						|
    bl_options = {'DEFAULT_CLOSED'}
 | 
						|
    bl_parent_id = "SCENE_PT_rigid_body_world"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def poll(cls, context):
 | 
						|
        return context.scene.rigidbody_world is not None
 | 
						|
 | 
						|
    def draw(self, context):
 | 
						|
        layout = self.layout
 | 
						|
        layout.use_property_split = True
 | 
						|
        layout.use_property_decorate = False
 | 
						|
        wrd = bpy.data.worlds['Lnx']
 | 
						|
 | 
						|
        if wrd.lnx_physics_engine != 'Bullet' and wrd.lnx_physics_engine != 'Oimo':
 | 
						|
            row = layout.row()
 | 
						|
            row.alert = True
 | 
						|
            row.label(text="Physics debug drawing is only supported for the Bullet and Oimo physics engines")
 | 
						|
 | 
						|
        col = layout.column(align=False)
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_wireframe")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_raycast")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_aabb")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_contact_points")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_constraints")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_constraint_limits")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_normals")
 | 
						|
        col.prop(wrd, "lnx_physics_dbg_draw_axis_gizmo")
 | 
						|
 | 
						|
def draw_custom_node_menu(self, context):
 | 
						|
    """Extension of the node context menu.
 | 
						|
 | 
						|
    https://blender.stackexchange.com/questions/150101/python-how-to-add-items-in-context-menu-in-2-8
 | 
						|
    """
 | 
						|
    if context.selected_nodes is None or len(context.selected_nodes) != 1:
 | 
						|
        return
 | 
						|
 | 
						|
    if context.space_data.tree_type == 'LnxLogicTreeType':
 | 
						|
        if context.selected_nodes[0].bl_idname.startswith('LN'):
 | 
						|
            layout = self.layout
 | 
						|
            layout.separator()
 | 
						|
            layout.operator("lnx.open_node_documentation", text="Show documentation for this node", icon='HELP')
 | 
						|
            layout.operator("lnx.open_node_haxe_source", text="Open .hx source in the browser", icon_value=ui_icons.get_id("haxe"))
 | 
						|
            layout.operator("lnx.open_node_python_source", text="Open .py source in the browser", icon='FILE_SCRIPT')
 | 
						|
 | 
						|
    elif context.space_data.tree_type == 'ShaderNodeTree':
 | 
						|
        if context.active_node.bl_idname in ('ShaderNodeRGB', 'ShaderNodeValue', 'ShaderNodeTexImage'):
 | 
						|
            layout = self.layout
 | 
						|
            layout.separator()
 | 
						|
            layout.prop(context.active_node, 'lnx_material_param', text='Leenkx: Material Parameter')
 | 
						|
 | 
						|
def draw_conditional_prop(layout: bpy.types.UILayout, heading: str, data: bpy.types.AnyType, prop_condition: str, prop_value: str) -> None:
 | 
						|
    """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)
 | 
						|
    row = col.row()
 | 
						|
    row.prop(data, prop_condition, text='')
 | 
						|
    sub = row.row()
 | 
						|
    sub.enabled = getattr(data, prop_condition)
 | 
						|
    sub.prop(data, prop_value, expand=True)
 | 
						|
 | 
						|
 | 
						|
def draw_error_box(layout: bpy.types.UILayout, text: str) -> bpy.types.UILayout:
 | 
						|
    """Draw an error box in the given UILayout and return it for
 | 
						|
    further optional modification. The text is wrapped automatically
 | 
						|
    according to the current region's width.
 | 
						|
    """
 | 
						|
    textwrap_width = max(0, int((bpy.context.region.width - 25) / 6))
 | 
						|
    lines = textwrap.wrap(text, width=textwrap_width, break_long_words=True)
 | 
						|
 | 
						|
    box = layout.box()
 | 
						|
    col = box.column(align=True)
 | 
						|
    col.alert = True
 | 
						|
    for idx, line in enumerate(lines):
 | 
						|
        col.label(text=line, icon='ERROR' if idx == 0 else 'BLANK1')
 | 
						|
 | 
						|
    return box
 | 
						|
 | 
						|
 | 
						|
def draw_multiline_with_icon(layout: bpy.types.UILayout, layout_width_px: int, icon: str, text: str) -> bpy.types.UILayout:
 | 
						|
    """Draw a multiline string with an icon in the given UILayout
 | 
						|
    and return it for further optional modification.
 | 
						|
    The text is wrapped according to the given layout width.
 | 
						|
    """
 | 
						|
    textwrap_width = max(0, layout_width_px // 6)
 | 
						|
    lines = textwrap.wrap(text, width=textwrap_width, break_long_words=True)
 | 
						|
 | 
						|
    col = layout.column(align=True)
 | 
						|
    col.scale_y = 0.8
 | 
						|
    for idx, line in enumerate(lines):
 | 
						|
        col.label(text=line, icon=icon if idx == 0 else 'BLANK1')
 | 
						|
 | 
						|
    return col
 | 
						|
 | 
						|
 | 
						|
__REG_CLASSES = (
 | 
						|
    LNX_PT_ObjectPropsPanel,
 | 
						|
    LNX_PT_ModifiersPropsPanel,
 | 
						|
    LNX_PT_ParticlesPropsPanel,
 | 
						|
    LNX_PT_PhysicsPropsPanel,
 | 
						|
    LNX_PT_DataPropsPanel,
 | 
						|
    LNX_PT_ScenePropsPanel,
 | 
						|
    LNX_PT_WorldPropsPanel,
 | 
						|
    InvalidateCacheButton,
 | 
						|
    InvalidateMaterialCacheButton,
 | 
						|
    LNX_OT_NewCustomMaterial,
 | 
						|
    LNX_PG_BindTexturesListItem,
 | 
						|
    LNX_UL_BindTexturesList,
 | 
						|
    LNX_OT_BindTexturesListNewItem,
 | 
						|
    LNX_OT_BindTexturesListDeleteItem,
 | 
						|
    LNX_PT_MaterialPropsPanel,
 | 
						|
    LNX_PT_BindTexturesPropsPanel,
 | 
						|
    LNX_PT_MaterialBlendingPropsPanel,
 | 
						|
    LNX_PT_MaterialDriverPropsPanel,
 | 
						|
    LNX_PT_LeenkxPlayerPanel,
 | 
						|
    LNX_PT_TopbarPanel,
 | 
						|
    LNX_PT_LeenkxExporterPanel,
 | 
						|
    LNX_PT_LeenkxExporterAndroidSettingsPanel,
 | 
						|
    LNX_PT_LeenkxExporterAndroidPermissionsPanel,
 | 
						|
    LNX_PT_LeenkxExporterAndroidAbiPanel,
 | 
						|
    LNX_PT_LeenkxExporterAndroidBuildAPKPanel,
 | 
						|
    LNX_PT_LeenkxExporterHTML5SettingsPanel,
 | 
						|
    LNX_PT_LeenkxExporterWindowsSettingsPanel,
 | 
						|
    LNX_PT_LeenkxProjectPanel,
 | 
						|
    LNX_PT_ProjectFlagsPanel,
 | 
						|
    LNX_PT_ProjectFlagsDebugConsolePanel,
 | 
						|
    LNX_PT_ProjectWindowPanel,
 | 
						|
    LNX_PT_ProjectModulesPanel,
 | 
						|
    LNX_PT_RenderPathPanel,
 | 
						|
    LNX_PT_RenderPathRendererPanel,
 | 
						|
    LNX_PT_RenderPathShadowsPanel,
 | 
						|
    LNX_PT_RenderPathVoxelsPanel,
 | 
						|
    LNX_PT_RenderPathWorldPanel,
 | 
						|
    LNX_PT_RenderPathPostProcessPanel,
 | 
						|
    LNX_PT_RenderPathCompositorPanel,
 | 
						|
    LNX_PT_BakePanel,
 | 
						|
    # LnxVirtualInputPanel,
 | 
						|
    LeenkxPlayButton,
 | 
						|
    LeenkxStopButton,
 | 
						|
    LeenkxBuildProjectButton,
 | 
						|
    LeenkxOpenProjectFolderButton,
 | 
						|
    LeenkxOpenEditorButton,
 | 
						|
    CleanMenu,
 | 
						|
    CleanButtonMenu,
 | 
						|
    LeenkxCleanProjectButton,
 | 
						|
    LeenkxPublishProjectButton,
 | 
						|
    LnxGenLodButton,
 | 
						|
    LNX_PT_LodPanel,
 | 
						|
    LnxGenTerrainButton,
 | 
						|
    LNX_PT_TerrainPanel,
 | 
						|
    LNX_PT_TilesheetPanel,
 | 
						|
    LnxPrintTraitsButton,
 | 
						|
    LNX_PT_MaterialNodePanel,
 | 
						|
    LNX_OT_UpdateFileSDK,
 | 
						|
    LNX_OT_CopyToBundled,
 | 
						|
    LNX_OT_ShowFileVersionInfo,
 | 
						|
    LNX_OT_ShowNodeUpdateErrors,
 | 
						|
    LNX_OT_DiscardPopup,
 | 
						|
    LeenkxUpdateListAndroidEmulatorButton,
 | 
						|
    LeenkxUpdateListAndroidEmulatorRunButton,
 | 
						|
    LeenkxUpdateListInstalledVSButton,
 | 
						|
    LNX_PT_PhysicsProps,
 | 
						|
    LNX_PT_PhysicsDebugDrawingPanel,
 | 
						|
    LNX_OT_AddArmatureRootMotion,
 | 
						|
    scene.TLM_PT_Settings,
 | 
						|
    scene.TLM_PT_Denoise,
 | 
						|
    scene.TLM_PT_Filtering,
 | 
						|
    scene.TLM_PT_Encoding,
 | 
						|
    scene.TLM_PT_Utility,
 | 
						|
    scene.TLM_PT_Additional,
 | 
						|
)
 | 
						|
__reg_classes, __unreg_classes = bpy.utils.register_classes_factory(__REG_CLASSES)
 | 
						|
 | 
						|
 | 
						|
def register():
 | 
						|
    __reg_classes()
 | 
						|
 | 
						|
    bpy.types.VIEW3D_HT_header.append(draw_view3d_header)
 | 
						|
    bpy.types.VIEW3D_MT_object.append(draw_view3d_object_menu)
 | 
						|
    bpy.types.NODE_MT_context_menu.append(draw_custom_node_menu)
 | 
						|
    bpy.types.TOPBAR_HT_upper_bar.prepend(draw_space_topbar)
 | 
						|
 | 
						|
    bpy.types.Material.lnx_bind_textures_list = CollectionProperty(type=LNX_PG_BindTexturesListItem)
 | 
						|
    bpy.types.Material.lnx_bind_textures_list_index = IntProperty(name='Index for lnx_bind_textures_list', default=0)
 | 
						|
 | 
						|
 | 
						|
def unregister():
 | 
						|
    bpy.types.NODE_MT_context_menu.remove(draw_custom_node_menu)
 | 
						|
    bpy.types.VIEW3D_MT_object.remove(draw_view3d_object_menu)
 | 
						|
    bpy.types.VIEW3D_HT_header.remove(draw_view3d_header)
 | 
						|
    bpy.types.TOPBAR_HT_upper_bar.remove(draw_space_topbar)
 | 
						|
 | 
						|
    __unreg_classes()
 |