# ##### BEGIN GPL LICENSE BLOCK #####
#
#  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 2
#  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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "Proxy Tools",
    "author": "xrogueleaderx",
    "version": (1, 1),
    "blender": (2, 80, 0),
    "location": "View3D > UI > Proxy Tools",
    "description": "Tool to create vertex cloud representations of highpoly objects",
    "warning": "",
    "wiki_url": "",
    "category": "Object",
}

#----------------------------------------------------------------------------------------------------------------------------------------
# Import Modules
#----------------------------------------------------------------------------------------------------------------------------------------
import bpy

from bpy.types import (
    AddonPreferences,
    Operator,
    Panel,
    PropertyGroup,)

from bpy.props import (
    FloatProperty,
    BoolProperty,)

from bpy.app.handlers import (
    persistent,)

#----------------------------------------------------------------------------------------------------------------------------------------
# Define Custom Properties
#----------------------------------------------------------------------------------------------------------------------------------------
class PROPS_UL_ProxyToolSettings(PropertyGroup):    
    #create float property for ReduceVerts
    bpy.types.Scene.ReduceVerts = bpy.props.FloatProperty(
        name = "ReduceVerts", 
        default =99.80, 
        min = 0.00, 
        max = 99.99, 
        precision = 2, 
        description = "Reduce vertices by percent", 
        subtype = "PERCENTAGE")

    #create string property for ProxyPath
    bpy.types.Scene.ProxyPath = bpy.props.StringProperty(
        name = "Proxy Path",
        default = "//proxy.blend",
        description = "Define the directory to store the proxy object file",
        subtype = 'FILE_PATH')

    #create bool property OffLoad
    bpy.types.Scene.OffLoad = bpy.props.BoolProperty(
        name = "OffLoad", 
        default=True, 
        description='Store collection data in separate library file')


#----------------------------------------------------------------------------------------------------------------------------------------
# Changing Viewport Display for Proxy Objects
#----------------------------------------------------------------------------------------------------------------------------------------
@persistent
def ProxyPreRenderHandler(scene):
    #store source and proxy objects in lists
    SourceObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" hi")]
    ProxyObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" lo")]
    
    for obj in bpy.context.scene.objects:
        if obj.type == 'EMPTY' and obj.name.endswith(" Proxy"):  
            obj.display_type = 'BOUNDS'

    for obj in SourceObjectList:
        obj.hide_viewport = False
        obj.display_type = 'BOUNDS'

    for obj in ProxyObjectList:
        obj.hide_viewport = True
        
@persistent
def ProxyPostRenderHandler(scene):
    #store source and proxy objects in lists
    SourceObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" hi")]
    ProxyObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" lo")] 
         
    for obj in SourceObjectList:
        obj.hide_viewport = True
        obj.display_type = 'SOLID'

    for obj in bpy.context.scene.objects:
        if obj.type == 'EMPTY' and obj.name.endswith(" Proxy"):  
                obj.display_type = 'SOLID'
                
    for obj in ProxyObjectList:
        obj.hide_viewport = False
        
class OBJECT_OT_ProxyPreRender(bpy.types.Operator):
    bl_idname = 'object.proxy_prerender'
    bl_label = 'Pre Render'
    bl_description = 'Set proxy objects display to bounds'
    
    def execute(self, context):
        ProxyPreRenderHandler(bpy.context.scene)
        return {'FINISHED'}

class OBJECT_OT_ProxyPostRender(bpy.types.Operator):
    bl_idname = 'object.proxy_postrender'
    bl_label = 'Post Render'
    bl_description = 'Set proxy objects display to vertex cloud'
    
    def execute(self, context):
        ProxyPostRenderHandler(bpy.context.scene)
        return {'FINISHED'}


#----------------------------------------------------------------------------------------------------------------------------------------
# Vertex Cloud Creation 
#----------------------------------------------------------------------------------------------------------------------------------------      
class OBJECT_OT_createproxy(bpy.types.Operator):
    bl_label = "Create Proxy"
    bl_idname = "object.create_proxy"
    bl_description = "Create vertex cloud proxy object from active object"  
       
    #should only work for mesh objects and curves
    @classmethod
    def poll(cls, context):
        return bpy.context.object.select_get() and bpy.context.object.type == 'MESH' or bpy.context.object.type == 'CURVE'
           
    def execute(self, context):        
        #store original location of source object
        originalLocation = [bpy.context.active_object.location[0], bpy.context.active_object.location[1],bpy.context.active_object.location[2]]

        #reset source object location to world origin
        bpy.ops.object.location_clear(clear_delta=True)

        #store source object data in variable
        SourceObject = bpy.context.active_object
        #create new collection with source object name and proxy object suffix
        bpy.ops.object.move_to_collection(collection_index=0, is_new=True, new_collection_name = SourceObject.name + ' Proxy')

        #duplicate source object to get proxy object
        bpy.ops.object.duplicate()
        #replace proxy object automatic suffix with lowpoly suffix    
        bpy.context.active_object.name = bpy.context.active_object.name.replace('.001',' lo')

        #store proxy object data in variable
        ProxyObject=bpy.context.active_object

        #apply all modifiers
        bpy.ops.object.convert(target='MESH')

        #create vertex cloud representation by deleting all edges and faces and high number of vertices
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.context.tool_settings.mesh_select_mode = [True, False, False]
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='EDGE_FACE')
        bpy.ops.mesh.select_random(percent=bpy.context.scene.ReduceVerts)
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode='OBJECT')

        #clear all material slots from proxy object // makes current Blender Release crash
        #ProxyObject.data.materials.clear()

        #set view modes as required
        ProxyObject.hide_render = True
        SourceObject.hide_viewport = True
        
        #if true, collection will be stored in separate .blend file
        if bpy.context.scene.OffLoad == True:
            SourceObjectName = SourceObject.name
            #add high poly suffix to source object
            SourceObject.name = SourceObject.name + ' hi'
        
            #set variables 
            filepath = bpy.context.scene.ProxyPath
            data_blocks = set(bpy.data.collections)

            #write proxy collection, source and proxy objects to new blend file
            bpy.data.libraries.write(filepath, data_blocks)

            #link all collections ending with ' Proxy'
            with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
                data_to.collections = [name for name in data_from.collections if name.endswith(" Proxy")]
            
            #create an empty to instance the linked proxy collection to
            bpy.ops.object.empty_add(type = 'PLAIN_AXES')
            #rename ceated empty accordingly
            bpy.context.active_object.name = SourceObjectName + " Proxy"
            EmptyObject = bpy.context.active_object

            #remove source and proxy object and original collection
            bpy.data.objects.remove(SourceObject, do_unlink = True)
            bpy.data.objects.remove(ProxyObject, do_unlink = True)
            bpy.data.collections.remove(collection = bpy.data.collections[EmptyObject.name])

            #instance linked proxy collection to empty
            bpy.context.object.instance_collection = bpy.data.collections[EmptyObject.name]
            #set empty instance type to collection to make proxy collection visible
            bpy.context.object.instance_type = 'COLLECTION'

            #place proxy collection at source object location
            bpy.context.active_object.location = originalLocation
        
        #if false, collection will be unlinked from scene but remain in current .blend file    
        else:    
            #create proxy collection instance in the scene
            bpy.ops.object.collection_instance_add(collection=SourceObject.name + ' Proxy')
            
            SourceObjectName = SourceObject.name
            #add high poly suffix to source object
            SourceObject.name = SourceObject.name + ' hi'
            
            #place proxy collection at source object location
            bpy.context.active_object.location = originalLocation

            #unlink collection from scene
            bpy.context.scene.collection.children.unlink(bpy.context.scene.collection.children[SourceObjectName + ' Proxy'])
        
        #clean file from unused accumulated datablocks    
        for block in bpy.data.meshes:
            if block.users == 0:
                bpy.data.meshes.remove(block)
                
        for block in bpy.data.materials:
            if block.users == 0:
                bpy.data.materials.remove(block)

        for block in bpy.data.textures:
            if block.users == 0:
                bpy.data.textures.remove(block)

        for block in bpy.data.images:
            if block.users == 0:
                bpy.data.images.remove(block)
        
        return {"FINISHED"}

    
#----------------------------------------------------------------------------------------------------------------------------------------
# UI Panel
#----------------------------------------------------------------------------------------------------------------------------------------  
class OBJECT_PT_proxypanel(bpy.types.Panel):
    # create panel in UI under category Proxy
    bl_label = "Create Proxy"
    bl_idname = "TOOLS_PT_CreateProxy"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Proxy Tools"
    bl_context = "objectmode"

    def draw(self, context):
        #store source and proxy objects in lists
        SourceObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" hi")]
        ProxyObjectList = [obj for obj in bpy.data.objects if obj.name.endswith(" lo")]
        
        layout = self.layout

        #create label
        layout.label(text = "Convert Object to Vertex Cloud")
        
        #create slider with ReduceVerts property
        layout.prop(bpy.context.scene, "ReduceVerts", text = "Vertex Reduction")
    
        #create directory selection
        row = layout.row()
        row.prop(bpy.context.scene, "ProxyPath")
    
        #create checkbox for off load geometry
        row = layout.row()
        row.prop(bpy.context.scene, "OffLoad", text = "Offload Geometry")
        
        #create conversion button
        row = layout.row()
        row.operator(OBJECT_OT_createproxy.bl_idname)
        
        layout.label(text = "Convert Scene")
        
        row = layout.row()
        #create pre render button
        row.operator(OBJECT_OT_ProxyPreRender.bl_idname, icon = "CUBE")
        #create post render button
        row.operator(OBJECT_OT_ProxyPostRender.bl_idname, icon = "STICKY_UVS_DISABLE")

       
#----------------------------------------------------------------------------------------------------------------------------------------
# Script Registration for Classes and Handlers
#----------------------------------------------------------------------------------------------------------------------------------------
classes = (
    PROPS_UL_ProxyToolSettings,
    OBJECT_PT_proxypanel,
    OBJECT_OT_createproxy,
    OBJECT_OT_ProxyPreRender,
    OBJECT_OT_ProxyPostRender,)

def register():
    from bpy.utils import register_class
    for cls in classes:
         register_class(cls)
    bpy.app.handlers.render_pre.append(ProxyPreRenderHandler)
    bpy.app.handlers.render_post.append(ProxyPostRenderHandler)

def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)
    bpy.app.handlers.render_pre.remove(ProxyPreRenderHandler)
    bpy.app.handlers.render_post.remove(ProxyPostRenderHandler)

if __name__ == "__main__":
    register()