'''
BlenderToMSTS

COPYRIGHT 2016 by Wayne Campbell        

	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.

    A copy of the GNU General Public License is included in this distribution package
    or may be found here <http://www.gnu.org/licenses/>.

For complete documentation, and CONTACT info see the Instructions included in the distribution package.    
    
REVISION HISTORY
2015-01-11      Released as V 0.1.2
                Fixed bug re RW Naming
2016-01-10      Released as V 0.1.1
                Added support for RW naming
2013-11-01      Initial Release as V 0.1.0
    
    
''' 
   
bl_info = {
    'name': 'MSTS Distance Level Selection (WAC)',
    'author': 'Wayne Campbell',
    'version': (0, 1, 2),
    "blender": (2, 6, 0),
    "api": 40791,
    "location": "View3D -> Properties",
    'description': 'Hide/Unhide objects per DLEVEL properties.',
    'category': '3D View'
    }

import bpy
 

dlevel = -1  # -1 means disabled, 0, 1, 2 represent DLEVEL1, DLEVEL2, DLEVEL3 etc

dlevelSetting = 'Off'  # this is the status string displayed in the Distance Level Selectio Panel


##############################
#   Menu in UI region
class DLevelPanel(bpy.types.Panel):
    bl_label = "Distance Level Selection"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self, context):  # called periodically to refresh menu
        row = self.layout.row()
        row.label( dlevelSetting )      # status label ie 'DLEVEL1 200 to 500M'
        self.layout.operator("display.dlevelselection", text='Select') # button
        


###########################################
# Get the empty or mesh object named 'MAIN'
def GetMainObject():
    
    for object in bpy.data.objects:
        if object.name == 'MAIN':
            return object
    return None

##############################
# MAIN object should have custom properties assigned like DLEVEL1=200, DLEVEL2=500 etc
# return a list of distances, ie [ 200, 500 ] etc
def GetLevelDistances( object ):
    
    levelDistances = []
    
    if object == None:
        return levelDistances
    
    for i in range( 0,10 ):
        dlname = 'DLEVEL'+str(i)
        dldistance = object.get( dlname, -1 )
        if dldistance != -1:
            levelDistances.append( dldistance )
            
    return levelDistances
    
        
##############################
# True if object is a descendant of ancestor, or is actually the ancestor
def IsRelatedTo( ancestor, object ):

    if object == None:
        return False    
    if object == ancestor:
        return True
    if object.parent == None:
        return False
    if object.parent == ancestor: 
        return True
    if IsRelatedTo( ancestor, object.parent ):
        return True
    return False
    
##############################
#   Sets the visibility for this object and all its descendants
#
def SetVisibility( object, lowerLimit ):

    if object.type == 'MESH' or object.is_duplicator:
        # hide the mesh object if its outside the visible limits for this DLEVEL
        if InDistanceLevel( object, lowerLimit):
            object.hide = False
            object.hide_render = False
        else:
            object.hide = True
            object.hide_render = True
            
    else: # Hide all the empties ( to keep the display cleaner )
            object.hide = True
            object.hide_render = True
            
    for objectChild in object.children:
        SetVisibility( objectChild, lowerLimit )

            
#####################################
def IsRWName( name ):

    # return true if eg 1_1000_xxxxx
    if len( name ) < 8 :  
        return False
    if not name[0].isdigit():
        return False
    if name[1] != '_':
        return False
    if not name[2].isdigit():
        return False
    if not name[3].isdigit():
        return False
    if not name[4].isdigit():
        return False
    if not name[5].isdigit():
        return False
    if name[6] != '_':
        return False
    
    return True

#####################################
# based on Custom Properties
def InViewingRange( objectMin, objectMax, levelMin ):

    if objectMax <= levelMin+0.001:  return False   # there's a lower detail LOD that would be better fit
    if objectMin >  levelMin+0.001:  return False   # there's a higher detail LOD that would be better fit
    return True
        
        
#####################################
# based on Custom Properties 
def InDistanceLevelProperties( meshObject, levelMin ):
    
    objectMin = meshObject.get('DMIN',0)
    objectMax = meshObject.get('DMAX',100000 )
    return InViewingRange( objectMin, objectMax, levelMin)
        
#####################################
# based on Railworks Naming
def InDistanceLevelRWNaming( meshObject, levelMin ):

    # this assumes successive LODs are children of the previous LOD
    objectMin = 0
    if meshObject.name[0] != '1':
        if meshObject.parent != None and IsRWName( meshObject.parent.name ): 
            objectMin = int( meshObject.parent.name[2:6] )
        else:
            print( "WARNING - RW LOD hierarchy incorrect for: " + meshObject.name)
        
    objectMax = int( meshObject.name[2:6] )
    
    return InViewingRange( objectMin, objectMax, levelMin)

    
#####################################
def InDistanceLevel( meshObject, levelMin ):

    if IsRWName( meshObject.name ):  # support Railworks style LOD naming
        return InDistanceLevelRWNaming( meshObject, levelMin)
    elif 'DMIN' in meshObject or 'DMAX' in meshObject:   # support Custom Properties
        return InDistanceLevelProperties( meshObject, levelMin)
    elif meshObject.parent != None:     # otherwise inherit from parent
        return InDistanceLevel( meshObject.parent, levelMin )
    else:                # if no restrictions, then it must be visible
        return True


    
##############################
#   The Select button executes here
#
class OBJECT_OT_DLevelButton(bpy.types.Operator):
    bl_idname = "display.dlevelselection"
    bl_label = "DLevelSelection"
    
    def execute(self, context):
        global dlevel
        global dlevelSetting
        
        # Make sure we have a MAIN object
        mainObject =  GetMainObject()
        if mainObject == None:
            dlevelSetting = "MAIN not defined"
            return {'FINISHED'}

        # See if DLEVEL1, DLEVEL2 custom properties are defined for MAIN
        levelDistances = GetLevelDistances( mainObject )
           # returns eg [200,500, 2000 ]
            
        # cycle the dlevel selector from 'disabled' through object available distance level
        dlevel = dlevel + 1
        if dlevel >= len( levelDistances ):
            dlevel = -1
        
        if dlevel == -1: 
            # selector is set to 'disable'
            if len( levelDistances ) == 0:
                dlevelSetting = "No DLEVELS defined in MAIN"
            else:
                dlevelSetting = "OFF"
            # restore visibility MAIN and all objects below MAIN
            for object in bpy.data.objects:
                if IsRelatedTo( mainObject, object ):
                    object.hide = False
                    object.hide_render = False
        else:
            # selector is set to one of the distance levels, hide the others
            # object DLEVEL has an upper and lower limit for visibility
            upperLimit = levelDistances[dlevel]
            # determine lower range for distance level
            lowerLimit = 0   
            if dlevel > 0:
                lowerLimit = levelDistances[dlevel-1]
            # this string is displayed on Distance Level Selection panel
            dlevelSetting = 'DLEVEL'+str(dlevel+1)+'  '+str(lowerLimit)+' to '+str(upperLimit)+'M'
            # for MAIN and every object below it
            main = bpy.context.scene.objects.get('MAIN')
            if main != None:
                SetVisibility( main, lowerLimit )
                            
        return{'FINISHED'}
 
##############################
#   Registration

def register():
    bpy.utils.register_module(__name__)
 
def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    unregister()
    register()
