# Copyright (c) 2017-2026 Soft8Soft, LLC. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
#
#
#
#
#
import bpy, bpy_extras

import os
import webbrowser

import pluginUtils
from pluginUtils.path import getAppManagerHost

log = pluginUtils.log.getLogger('V3D-BL')

from . import utils

join = os.path.join

from pluginUtils.manager import AppManagerConn

class V3DPanel():
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'

    COMPAT_ENGINES = ['CYCLES', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT']

    @classmethod
    def poll(cls, context):

        if (cls.poll_datablock == 'clipping_plane' and context.object and
                context.object.type == 'EMPTY' and context.object.v3d.clipping_plane):
            return True

        elif (cls.poll_datablock == 'lightprobe_sphere' and context.object and
                context.object.type == 'LIGHT_PROBE' and context.object.data and
                context.object.data.type == 'SPHERE'):
            return True

        elif (cls.poll_datablock == 'lightprobe_plane' and context.object and
              context.object.type == 'LIGHT_PROBE' and context.object.data and
              context.object.data.type == 'PLANE'):
            return True

        elif (hasattr(context, cls.poll_datablock) and
                getattr(context, cls.poll_datablock) and
                context.scene.render.engine in cls.COMPAT_ENGINES):
            return True

        else:
            return False

class V3D_PT_RenderSettings(bpy.types.Panel, V3DPanel):
    """Located on render panel"""
    bl_context = 'render'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'scene'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d_export = bpy.data.scenes[0].v3d_export

        row = layout.row()
        row.prop(v3d_export, 'copyright')

        row = layout.row()
        row.prop(v3d_export, 'export_constraints')

        row = layout.row()
        row.prop(v3d_export, 'export_custom_props')

        row = layout.row()
        row.prop(v3d_export, 'bake_modifiers')

        row = layout.row()
        row.prop(v3d_export, 'bake_text')

        row = layout.row()
        row.prop(v3d_export, 'lzma_enabled')

        row = layout.row()
        row.prop(v3d_export, 'compress_textures')

        row = layout.row()
        row.prop(v3d_export, 'optimize_attrs')

        row = layout.row()
        row.prop(v3d_export, 'aa_method')

        row = layout.row()
        row.prop(v3d_export, 'use_hdr')

        row = layout.row()
        row.prop(v3d_export, 'use_oit')

        row = layout.row()
        row.prop(context.scene.eevee, 'gi_cubemap_resolution', text='Env. Map Size')

        row = layout.row()
        row.prop(v3d_export, 'ibl_environment_mode')

class COLLECTION_UL_export(bpy.types.UIList):

    def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag):
        col = layout.column()
        col.prop(item, 'name', text='', emboss=False, icon_value=icon)
        col = layout.column()
        col.prop(item.v3d, 'enable_export', text='')

    def filter_items(self, context, data, property):
        collections = getattr(data, property)
        helper_funcs = bpy.types.UI_UL_list

        flt_flags = []
        flt_neworder = []

        if self.filter_name:
            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item,
                                                          collections, 'name', reverse=self.use_filter_invert)
        if not flt_flags:
            flt_flags = [self.bitflag_filter_item] * len(collections)

        if self.use_filter_sort_alpha:
            flt_neworder = helper_funcs.sort_items_by_name(collections, 'name')
            if self.use_filter_sort_reverse:
                flt_neworder.reverse()

        return flt_flags, flt_neworder

class V3D_PT_RenderSettingsAnimation(bpy.types.Panel, V3DPanel):
    bl_label = 'Animation'
    bl_parent_id = 'V3D_PT_RenderSettings'

    poll_datablock = 'scene'

    def draw_header(self, context):
        v3d_export = bpy.data.scenes[0].v3d_export
        self.layout.prop(v3d_export, 'export_animations', text='')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d_export = bpy.data.scenes[0].v3d_export
        layout.active = v3d_export.export_animations

        row = layout.row()
        row.prop(v3d_export, 'export_frame_range')
        row = layout.row()
        row.prop(v3d_export, 'export_move_keyframes')
        row = layout.row()
        row.prop(v3d_export, 'bake_armature_actions')

class V3D_PT_RenderSettingsShadows(bpy.types.Panel, V3DPanel):
    bl_label = 'Shadows'
    bl_parent_id = 'V3D_PT_RenderSettings'

    poll_datablock = 'scene'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        scene0 = bpy.data.scenes[0]
        v3d_export = scene0.v3d_export

        layout.active = scene0.eevee.use_shadows

        row = layout.row()
        row.prop(v3d_export, 'shadow_map_type')

        row = layout.row()
        row.prop(v3d_export, 'shadow_map_side')

        row = layout.row()
        row.prop(v3d_export, 'esm_distance_scale')
        row.active = v3d_export.shadow_map_type == 'ESM'

        row = layout.row()
        row.prop(v3d_export, 'shadow_cube_size')

        row = layout.row()
        row.prop(v3d_export, 'shadow_cascade_size')

class V3D_PT_RenderSettingsOutline(bpy.types.Panel, V3DPanel):
    bl_label = 'Outline Effect'
    bl_parent_id = 'V3D_PT_RenderSettings'
    bl_options = {'DEFAULT_CLOSED'}

    poll_datablock = 'scene'

    def draw_header(self, context):
        outline = context.scene.v3d.outline
        self.layout.prop(outline, 'enabled', text='')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        outline = context.scene.v3d.outline
        outlineActive = outline.enabled

        layout.active = outlineActive

        row = layout.row()
        row.prop(outline, 'edge_strength')
        row = layout.row()
        row.prop(outline, 'edge_glow')
        row = layout.row()
        row.prop(outline, 'edge_thickness')
        row = layout.row()
        row.prop(outline, 'pulse_period')
        row = layout.row()
        row.prop(outline, 'visible_edge_color')
        row = layout.row()
        row.prop(outline, 'hidden_edge_color')
        row = layout.row()
        row.prop(outline, 'render_hidden_edge')

class V3D_PT_RenderSettingsGTAO(bpy.types.Panel, V3DPanel):
    bl_label = 'Ambient Occlusion'
    bl_parent_id = 'V3D_PT_RenderSettings'
    bl_options = {'DEFAULT_CLOSED'}

    poll_datablock = 'scene'

    def draw_header(self, context):
        if bpy.app.version < (5, 0, 0):
            self.layout.prop(context.scene.eevee, 'use_gtao', text='')
        else:
            self.layout.prop(context.scene.v3d, 'use_gtao', text='')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        eevee = context.scene.eevee
        v3d = context.scene.v3d

        if bpy.app.version < (5, 0, 0):
            layout.active = eevee.use_gtao
        else:
            layout.active = v3d.use_gtao

        row = layout.row()
        if bpy.app.version < (5, 0, 0):
            row.prop(eevee, 'gtao_distance')
        else:
            row.prop(v3d, 'gtao_distance')

        row = layout.row()
        if bpy.app.version < (4, 3, 0):
            row.prop(eevee, 'gtao_factor')
        else:
            row.prop(context.scene.v3d, 'gtao_factor')

        row = layout.row()
        if bpy.app.version < (5, 0, 0):
            row.prop(eevee, 'gtao_quality')
        else:
            row.prop(v3d, 'gtao_quality')

        row = layout.row()
        if bpy.app.version < (4, 3, 0):
            row.prop(eevee, 'use_gtao_bent_normals')
        else:
            row.prop(context.scene.v3d, 'use_gtao_bent_normals')

class V3D_PT_RenderSettingsCollections(bpy.types.Panel, V3DPanel):
    bl_label = 'Export Collections'
    bl_parent_id = 'V3D_PT_RenderSettings'
    bl_options = {'DEFAULT_CLOSED'}

    poll_datablock = 'scene'

    def draw(self, context):
        layout = self.layout
        layout.template_list('COLLECTION_UL_export', '', bpy.data,
                'collections', context.scene.v3d_export, 'collections_exported_idx', rows=4)

class V3D_PT_WorldSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'world'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'world'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        world = context.world

        row = layout.row()
        row.prop(world.v3d, 'dithering')

class V3D_PT_ObjectSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'object'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'object'

    def draw(self, context):
        pass

class V3D_PT_ObjectSettingsAnimation(bpy.types.Panel, V3DPanel):
    bl_label = 'Animation'
    bl_parent_id = 'V3D_PT_ObjectSettings'

    poll_datablock = 'object'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d = context.object.v3d

        row = layout.row()
        row.prop(v3d, 'anim_auto')

        row = layout.row()
        row.prop(v3d, 'anim_loop')

        row = layout.row()
        row.active = (v3d.anim_loop != 'ONCE')
        row.prop(v3d, 'anim_repeat_infinite')

        row = layout.row()
        row.active = (v3d.anim_loop != 'ONCE' and not v3d.anim_repeat_infinite)
        row.prop(v3d, 'anim_repeat_count')

        row = layout.row()
        row.prop(v3d, 'anim_offset')

class V3D_PT_ObjectSettingsRendering(bpy.types.Panel, V3DPanel):
    bl_label = 'Rendering'
    bl_parent_id = 'V3D_PT_ObjectSettings'

    poll_datablock = 'object'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and
                context.object.type in ['MESH', 'CURVE', 'SURFACE', 'META', 'FONT'])

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        obj = context.object
        v3d = obj.v3d

        row = layout.row()
        row.prop(v3d, 'render_order')

        row = layout.row()
        row.prop(v3d, 'frustum_culling')

        row = layout.row()
        row.prop(v3d, 'use_shadows')

        row = layout.row()
        row.prop(v3d, 'hidpi_compositing')

        if obj.parent and obj.parent.type == 'CAMERA':
            if obj.parent.data.type == 'ORTHO':
                row = layout.row()
                row.prop(v3d, 'fix_ortho_zoom')

class V3D_PT_ObjectSettingsChildRendering(bpy.types.Panel, V3DPanel):
    bl_label = 'Child Rendering'
    bl_parent_id = 'V3D_PT_ObjectSettings'

    poll_datablock = 'object'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and not
                context.object.type in ['MESH', 'CURVE', 'SURFACE', 'META', 'FONT'])

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d = context.object.v3d

        row = layout.row()
        row.prop(v3d, 'hidpi_compositing')

class V3D_PT_ObjectSettingsVisibilityBreakpoints(bpy.types.Panel, V3DPanel):
    bl_label = 'Visibility Breakpoints'
    bl_parent_id = 'V3D_PT_ObjectSettings'

    poll_datablock = 'object'

    def draw_header(self, context):
        v3d = context.object.v3d
        self.layout.prop(v3d, 'canvas_break_enabled', text='')

    def draw(self, context):
        layout = self.layout

        v3d = context.object.v3d

        brkpnts = v3d.canvas_break_enabled

        row = layout.row()
        row.active = brkpnts
        row.prop(v3d, 'canvas_break_min_width')
        row.prop(v3d, 'canvas_break_max_width')

        row = layout.row()
        row.active = brkpnts
        row.prop(v3d, 'canvas_break_min_height')
        row.prop(v3d, 'canvas_break_max_height')

        split = layout.split()
        split.active = brkpnts
        col = split.column()
        col.alignment = 'RIGHT'
        col.label(text='Orientation')
        col = split.column()
        col.prop(v3d, 'canvas_break_orientation', text='')

class V3D_PT_ObjectSettingsFitCameraEdge(bpy.types.Panel, V3DPanel):
    bl_label = 'Fit to Camera Edge'
    bl_parent_id = 'V3D_PT_ObjectSettings'

    poll_datablock = 'object'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and
                context.object.parent and context.object.parent.type == 'CAMERA')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d = context.object.v3d

        layout.prop(v3d, 'canvas_fit_x')
        layout.prop(v3d, 'canvas_fit_y')
        layout.prop(v3d, 'canvas_fit_shape')
        layout.prop(v3d, 'canvas_fit_offset')

        if ((v3d.canvas_fit_x != 'NONE' or v3d.canvas_fit_y != 'NONE') and
                not utils.mat4IsIdentity(context.object.matrix_parent_inverse)):
            layout.label(text='Clear parent inverse for correct positioning', icon='ERROR')

class V3D_PT_CameraSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'camera'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        camera = context.camera
        v3d = camera.v3d

        row = layout.row()
        row.prop(v3d, 'controls')

        if v3d.controls == 'FIRST_PERSON':
            row = layout.row()
            row.prop(v3d, 'fps_collision_material')

            row = layout.row()
            row.prop(v3d, 'fps_gaze_level')

            row = layout.row()
            row.prop(v3d, 'fps_story_height')

            row = layout.row()
            row.prop(v3d, 'enable_pointer_lock')

        row = layout.row()
        row.active = (v3d.controls != 'NONE')
        row.prop(v3d, 'enable_pan')

        row = layout.row()
        row.active = (v3d.controls != 'NONE')
        row.prop(v3d, 'rotate_speed')

        row = layout.row()
        row.active = (v3d.controls != 'NONE')
        row.prop(v3d, 'move_speed')

class V3D_PT_CameraSettingsTarget(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Target Object / Point'
    bl_parent_id = 'V3D_PT_CameraSettings'

    poll_datablock = 'camera'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and
                context.camera.v3d.controls == 'ORBIT')

    def draw(self, context):
        layout = self.layout

        camera = context.camera
        v3d = camera.v3d

        split = layout.split(factor=0.5)

        column = split.column()
        column.prop(v3d, 'orbit_target', text='Manual')
        column.enabled = v3d.orbit_target_object is None

        column = split.column()
        column.label(text='From Object:')
        column.prop(v3d, 'orbit_target_object', text='')

        column.operator(V3D_OT_orbit_camera_target_from_cursor.bl_idname, text='From Cursor')
        column.prop(v3d, 'orbit_target_show')

        layout.operator(V3D_OT_orbit_camera_update_view.bl_idname, text='Update View')

        row = layout.row()

        if camera.type == 'ORTHO':
            row.prop(v3d, 'orbit_min_zoom')
            row.prop(v3d, 'orbit_max_zoom')
        else:
            row.prop(v3d, 'orbit_min_distance')
            row.prop(v3d, 'orbit_max_distance')

        row = layout.row()
        row.label(text='Vertical Rotation Limits:')

        row = layout.row()
        row.prop(v3d, 'orbit_min_polar_angle')
        row.prop(v3d, 'orbit_max_polar_angle')

        row = layout.row()
        row.label(text='Horizontal Rotation Limits:')

        row = layout.row()
        row.prop(v3d, 'orbit_min_azimuth_angle')
        row.prop(v3d, 'orbit_max_azimuth_angle')

class V3D_PT_LightSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'light'

    def draw(self, context):
        pass

class V3D_PT_LightSettingsShadow(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Shadows'
    bl_parent_id = 'V3D_PT_LightSettings'

    poll_datablock = 'light'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        light = context.light
        type = light.type
        shadow = light.v3d.shadow

        row = layout.row()
        row.active = (context.scene.v3d_export.shadow_map_type
                not in ['BASIC', 'BILINEAR'])
        row.prop(shadow, 'radius', text='Blur Radius')

        if context.light.type in ('POINT', 'SPOT', 'AREA'):
            row = layout.row()
            row.prop(shadow, 'buffer_clip_start', text='Clip Start')

        row = layout.row()
        row.prop(shadow, 'buffer_bias', text='Bias')

        row = layout.row()
        row.active = context.scene.v3d_export.shadow_map_type == 'ESM'
        row.prop(shadow, 'esm_exponent', text='ESM Bias')

class V3D_PT_LightSettingsShadowCSM(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Cascaded Shadow Map'
    bl_parent_id = 'V3D_PT_LightSettingsShadow'

    poll_datablock = 'light'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and context.light.type == 'SUN')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        light = context.light
        type = light.type
        shadow = light.v3d.shadow

        row = layout.row()
        row.prop(shadow, 'cascade_count', text='Count')

        row = layout.row()
        row.prop(shadow, 'cascade_fade', text='Fade')

        row = layout.row()
        row.prop(shadow, 'cascade_max_distance', text='Max Distance')

        row = layout.row()
        row.prop(shadow, 'cascade_exponent', text='Distribution')

        row = layout.row()
        row.prop(shadow, 'csm_light_margin', text='Cascade Margin')

class V3D_PT_CurveSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'curve'

    @classmethod
    def poll(cls, context):
        return (super().poll(context) and not
                isinstance(getattr(context, cls.poll_datablock), bpy.types.TextCurve))

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d = context.curve.v3d

        box = layout.box()
        row = box.row()
        row.prop(v3d.line_rendering_settings, 'enable')

        is_active = getattr(v3d.line_rendering_settings, 'enable') is True

        row = box.row()
        row.prop(v3d.line_rendering_settings, 'color')
        row.active = is_active

        row = box.row()
        row.prop(v3d.line_rendering_settings, 'width')
        row.active = is_active

class V3D_PT_MeshSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'mesh'

    def draw(self, context):
        pass

class V3D_PT_MeshSettingsLineRendering(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Line Rendering'
    bl_parent_id = 'V3D_PT_MeshSettings'

    poll_datablock = 'mesh'

    def draw_header(self, context):
        v3d = context.mesh.v3d
        self.layout.prop(v3d.line_rendering_settings, 'enable', text='')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        v3d = context.mesh.v3d

        layout.active = getattr(v3d.line_rendering_settings, 'enable') is True

        row = layout.row()
        row.prop(v3d.line_rendering_settings, 'color')

        row = layout.row()
        row.prop(v3d.line_rendering_settings, 'width')

class V3D_PT_LightProbeSphereSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'lightprobe_sphere'

    def draw(self, context):
        v3d = context.lightprobe.v3d
        layout = self.layout
        layout.use_property_split = True

        row = layout.row(align=True)
        row.prop(v3d, 'intensity')

        row = layout.row(align=True)
        row.prop(context.lightprobe, 'visibility_collection')
        row.prop(context.lightprobe, 'invert_visibility_collection', text='', icon='ARROW_LEFTRIGHT')

class V3D_PT_LightProbeSphereSettingsCustomInfluence(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Custom Influence'
    bl_parent_id = 'V3D_PT_LightProbeSphereSettings'

    poll_datablock = 'lightprobe_sphere'

    def draw_header(self, context):
        v3d = context.lightprobe.v3d
        self.layout.prop(v3d, 'use_custom_influence', text='')

    def draw(self, context):
        layout = self.layout

        v3d = context.lightprobe.v3d
        layout.active = getattr(v3d, 'use_custom_influence') is True

        row = layout.row(align=True)
        row.use_property_split = True
        row.prop(v3d, 'influence_collection')
        row.prop(v3d, 'invert_influence_collection', text='', icon='ARROW_LEFTRIGHT')

class V3D_PT_LightProbePlaneSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'lightprobe_plane'

    def draw(self, context):
        v3d = context.lightprobe.v3d
        layout = self.layout
        layout.use_property_split = True

        row = layout.row(align=True)
        row.prop(v3d, 'falloff')

        row = layout.row(align=True)
        row.prop(context.lightprobe, 'visibility_collection')
        row.prop(context.lightprobe, 'invert_visibility_collection', text='', icon='ARROW_LEFTRIGHT')

class V3D_PT_ClippingPlaneSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Verge3D Clipping Plane'

    poll_datablock = 'clipping_plane'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        plane = context.object

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_collection')

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_negated')

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_shadows')

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_union')

class V3D_PT_ClippingPlaneSettingsCrossSection(bpy.types.Panel, V3DPanel):
    bl_context = 'data'
    bl_label = 'Filled Cross-Section'
    bl_parent_id = 'V3D_PT_ClippingPlaneSettings'

    poll_datablock = 'clipping_plane'

    def draw_header(self, context):
        layout = self.layout

        plane = context.object
        layout.active = plane.v3d.clipping_plane_cross_section and plane.v3d.clipping_plane_union

        layout.prop(plane.v3d, 'clipping_plane_cross_section', text='')

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        plane = context.object
        layout.active = plane.v3d.clipping_plane_cross_section and plane.v3d.clipping_plane_union

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_color', text='Color')

        row = layout.row()
        row.prop(plane.v3d, 'clipping_plane_render_side', text='Render Side')

        row = layout.split()
        row.prop(plane.v3d, 'clipping_plane_size', text='Plane Size')

class V3D_PT_MaterialSettings(bpy.types.Panel, V3DPanel):
    bl_context = 'material'
    bl_label = 'Verge3D Settings'

    poll_datablock = 'material'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        material = context.material

        row = layout.row()
        row.prop(material.v3d, 'blend_method')

        row = layout.row()
        row.prop(material, 'alpha_threshold')
        row.active = material.v3d.blend_method == 'CLIP'

        row = layout.row()
        row.prop(material.v3d, 'transparency_hack')
        row.active = material.v3d.blend_method == 'BLEND'

        row = layout.row()
        row.prop(material.v3d, 'render_side')

        row = layout.row()
        row.prop(material.v3d, 'depth_write')
        row.active = not material.v3d.blend_method == 'OPAQUE'

        row = layout.row()
        row.prop(material.v3d, 'depth_test')

        row = layout.row()
        row.prop(material.v3d, 'dithering')

        row = layout.row()
        row.prop(material.v3d, 'gltf_compat')

class V3D_PT_NodeSettings(bpy.types.Panel):
    bl_space_type = 'NODE_EDITOR'
    bl_region_type = 'UI'
    bl_label = 'Verge3D Settings'
    bl_parent_id = 'NODE_PT_active_node_generic'

    @classmethod
    def poll(cls, context):
        node = context.active_node
        return node is not None and (
                isinstance(node, bpy.types.ShaderNodeTexImage)
                or isinstance(node, bpy.types.ShaderNodeTexEnvironment)
                or isinstance(node, bpy.types.ShaderNodeTexNoise)
        )

    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        node = context.active_node

        if isinstance(node, bpy.types.ShaderNodeTexImage):

            row = layout.row()
            row.label(text='Anisotropic Filtering:')

            row = layout.row()
            row.prop(node.v3d, 'anisotropy', text='Ratio')

            image = node.image
            if image:
                row = layout.row()
                row.label(text='Texture Compression:')

                row = layout.row()
                row.prop(image.v3d, 'compression_method', text='Method')

        elif isinstance(node, bpy.types.ShaderNodeTexEnvironment):
            image = node.image
            if image:
                row = layout.row()
                row.label(text='Texture Compression:')

                row = layout.row()
                row.prop(image.v3d, 'compression_method', text='Hint')

        elif isinstance(node, bpy.types.ShaderNodeTexNoise):

            row = layout.row()
            row.label(text='Noise Parameters:')

            row = layout.row()
            row.prop(node.v3d, 'falloff_factor')

            row = layout.row()
            row.prop(node.v3d, 'dispersion_factor')

def execBrowser(url):
    try:
        webbrowser.open(url)
    except BaseException:
        print("Failed to open URL: " + url)

class V3D_OT_orbit_camera_target_from_cursor(bpy.types.Operator):
    bl_idname = 'camera.v3d_orbit_camera_target_from_cursor'
    bl_label = 'From Cursor'
    bl_description = 'Update target coordinates from cursor position'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        context.object.data.v3d.orbit_target_object = None
        context.object.data.v3d.orbit_target = bpy.context.scene.cursor.location
        utils.updateOrbitCameraView(context.object, context.scene)
        return {'FINISHED'}

class V3D_OT_orbit_camera_update_view(bpy.types.Operator):
    bl_idname = 'camera.v3d_orbit_camera_update_view'
    bl_label = 'Update View'
    bl_description = 'Update view for the orbit camera'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        utils.updateOrbitCameraView(context.object, context.scene)
        return {'FINISHED'}

class V3D_OT_sneak_peek(bpy.types.Operator):
    bl_idname = 'view3d.v3d_sneak_peek'
    bl_label = 'Sneak Peek'
    bl_description = 'Export to temporary location and preview the scene in Verge3D'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        if not AppManagerConn.ping():
            AppManagerConn.start()

        try:
            prevDir = AppManagerConn.getPreviewDir(True)
        except (PermissionError, FileExistsError):
            self.report({'ERROR'}, 'Permission denied when attempting to access the preview folder.\n'
                    'Make sure that you\'re not running the App Manager under different user.')
            return {'CANCELLED'}

        bpy.ops.export_scene.v3d_gltf(filepath=join(prevDir, 'sneak_peek.gltf'),
                                export_sneak_peek = True)

        execBrowser(getAppManagerHost('BLENDER') +
                'player/player.html?load=/sneak_peek/sneak_peek.gltf')

        return {'FINISHED'}

class V3D_OT_app_manager(bpy.types.Operator):
    bl_idname = 'view3d.v3d_app_manager'
    bl_label = 'Open App Manager'
    bl_description = 'Open Verge3D App Manager'
    bl_options = {'INTERNAL'}

    def execute(self, context):
        if not AppManagerConn.ping():
            AppManagerConn.start()
        execBrowser(getAppManagerHost('BLENDER'))
        return {"FINISHED"}

def btnSneakPeek(self, context):
    self.layout.operator(V3D_OT_sneak_peek.bl_idname, text='Sneak Peek', icon='HIDE_OFF')

def btnAppManager(self, context):
    self.layout.operator(V3D_OT_app_manager.bl_idname, text='App Manager', icon='WORDWRAP_ON')

def menuUserManual(self, context):
    if context.scene.render.engine in V3DPanel.COMPAT_ENGINES:
        self.layout.separator()
        self.layout.operator('wm.url_open', text='Verge3D User Manual', icon='URL').url = AppManagerConn.getManualURL()

class VIEW3D_MT_verge3d_add(bpy.types.Menu):
    bl_idname = 'VIEW3D_MT_verge3d_add'
    bl_label = 'Verge3D'

    def draw(self, context):
        self.layout.operator('object.add_clipping_plane', icon='AXIS_TOP')

class V3D_OT_add_clipping_plane(bpy.types.Operator, bpy_extras.object_utils.AddObjectHelper):
    bl_idname = 'object.add_clipping_plane'
    bl_label = 'Clipping Plane'
    bl_options = {'REGISTER', 'UNDO', 'PRESET', 'INTERNAL'}
    bl_description = 'Construct clipping plane'

    def execute(self, context):

        obj = bpy.data.objects.new('ClippingPlane', None)
        context.view_layer.active_layer_collection.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        bpy.ops.object.select_all(action='DESELECT')
        obj.select_set(True)

        obj.empty_display_type = 'ARROWS'
        obj.v3d.clipping_plane = True

        return {'FINISHED'}

def menuVerge3dAdd(self, context):
    self.layout.menu(VIEW3D_MT_verge3d_add.bl_idname, icon='SOLO_ON')

def register():

    bpy.utils.register_class(VIEW3D_MT_verge3d_add)
    bpy.utils.register_class(V3D_OT_add_clipping_plane)

    bpy.types.VIEW3D_MT_add.append(menuVerge3dAdd)
    bpy.types.TOPBAR_MT_help.append(menuUserManual)

    bpy.utils.register_class(V3D_PT_RenderSettings)
    bpy.utils.register_class(V3D_PT_RenderSettingsAnimation)
    bpy.utils.register_class(V3D_PT_RenderSettingsShadows)
    bpy.utils.register_class(V3D_PT_RenderSettingsGTAO)
    bpy.utils.register_class(V3D_PT_RenderSettingsOutline)
    bpy.utils.register_class(V3D_PT_RenderSettingsCollections)
    bpy.utils.register_class(V3D_PT_WorldSettings)
    bpy.utils.register_class(V3D_PT_ObjectSettings)
    bpy.utils.register_class(V3D_PT_ObjectSettingsAnimation)
    bpy.utils.register_class(V3D_PT_ObjectSettingsRendering)
    bpy.utils.register_class(V3D_PT_ObjectSettingsChildRendering)
    bpy.utils.register_class(V3D_PT_ObjectSettingsVisibilityBreakpoints)
    bpy.utils.register_class(V3D_PT_ObjectSettingsFitCameraEdge)
    bpy.utils.register_class(V3D_PT_CameraSettings)
    bpy.utils.register_class(V3D_PT_CameraSettingsTarget)
    bpy.utils.register_class(V3D_PT_LightSettings)
    bpy.utils.register_class(V3D_PT_LightSettingsShadow)
    bpy.utils.register_class(V3D_PT_LightSettingsShadowCSM)
    bpy.utils.register_class(V3D_PT_ClippingPlaneSettings)
    bpy.utils.register_class(V3D_PT_ClippingPlaneSettingsCrossSection)
    bpy.utils.register_class(V3D_PT_MaterialSettings)
    bpy.utils.register_class(V3D_PT_CurveSettings)
    bpy.utils.register_class(V3D_PT_MeshSettings)
    bpy.utils.register_class(V3D_PT_MeshSettingsLineRendering)
    bpy.utils.register_class(V3D_PT_NodeSettings)
    bpy.utils.register_class(V3D_PT_LightProbeSphereSettings)
    bpy.utils.register_class(V3D_PT_LightProbeSphereSettingsCustomInfluence)
    bpy.utils.register_class(V3D_PT_LightProbePlaneSettings)

    bpy.utils.register_class(V3D_OT_orbit_camera_target_from_cursor)
    bpy.utils.register_class(V3D_OT_orbit_camera_update_view)

    bpy.utils.register_class(COLLECTION_UL_export)

    if AppManagerConn.isAvailable():
        bpy.utils.register_class(V3D_OT_sneak_peek)
        bpy.utils.register_class(V3D_OT_app_manager)
        bpy.types.VIEW3D_HT_header.append(btnSneakPeek)
        bpy.types.VIEW3D_HT_header.append(btnAppManager)

def unregister():

    if AppManagerConn.isAvailable():
        bpy.types.VIEW3D_HT_header.remove(btnSneakPeek)
        bpy.types.VIEW3D_HT_header.remove(btnAppManager)
        bpy.utils.unregister_class(V3D_OT_sneak_peek)
        bpy.utils.unregister_class(V3D_OT_app_manager)

    bpy.utils.unregister_class(V3D_PT_NodeSettings)
    bpy.utils.unregister_class(V3D_PT_ClippingPlaneSettings)
    bpy.utils.unregister_class(V3D_PT_ClippingPlaneSettingsCrossSection)
    bpy.utils.unregister_class(V3D_PT_MaterialSettings)
    bpy.utils.unregister_class(V3D_PT_LightSettings)
    bpy.utils.unregister_class(V3D_PT_LightSettingsShadow)
    bpy.utils.unregister_class(V3D_PT_LightSettingsShadowCSM)
    bpy.utils.unregister_class(V3D_PT_CurveSettings)
    bpy.utils.unregister_class(V3D_PT_CameraSettings)
    bpy.utils.unregister_class(V3D_PT_CameraSettingsTarget)
    bpy.utils.unregister_class(V3D_PT_ObjectSettings)
    bpy.utils.unregister_class(V3D_PT_ObjectSettingsAnimation)
    bpy.utils.unregister_class(V3D_PT_ObjectSettingsRendering)
    bpy.utils.unregister_class(V3D_PT_ObjectSettingsChildRendering)
    bpy.utils.unregister_class(V3D_PT_ObjectSettingsVisibilityBreakpoints)
    bpy.utils.unregister_class(V3D_PT_ObjectSettingsFitCameraEdge)
    bpy.utils.unregister_class(V3D_PT_WorldSettings)
    bpy.utils.unregister_class(V3D_PT_RenderSettings)
    bpy.utils.unregister_class(V3D_PT_RenderSettingsAnimation)
    bpy.utils.unregister_class(V3D_PT_RenderSettingsShadows)
    bpy.utils.unregister_class(V3D_PT_RenderSettingsGTAO)
    bpy.utils.unregister_class(V3D_PT_RenderSettingsOutline)
    bpy.utils.unregister_class(V3D_PT_RenderSettingsCollections)
    bpy.utils.unregister_class(V3D_PT_MeshSettings)
    bpy.utils.unregister_class(V3D_PT_MeshSettingsLineRendering)
    bpy.utils.unregister_class(V3D_PT_LightProbeSphereSettings)
    bpy.utils.unregister_class(V3D_PT_LightProbeSphereSettingsCustomInfluence)
    bpy.utils.unregister_class(V3D_PT_LightProbePlaneSettings)

    bpy.utils.unregister_class(V3D_OT_orbit_camera_target_from_cursor)
    bpy.utils.unregister_class(V3D_OT_orbit_camera_update_view)

    bpy.utils.unregister_class(COLLECTION_UL_export)

    bpy.types.TOPBAR_MT_help.remove(menuUserManual)
    bpy.types.VIEW3D_MT_add.remove(menuVerge3dAdd)

    bpy.utils.unregister_class(V3D_OT_add_clipping_plane)
    bpy.utils.unregister_class(VIEW3D_MT_verge3d_add)
