# ##### 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, 0),
    "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 bpy

from bpy.types import (
    AddonPreferences,
    Operator,
    Panel,
    PropertyGroup,
)

from bpy.props import (
    FloatProperty,
    BoolProperty,
)

from bpy.app.handlers import (
    persistent,
)


#########################################################################################################################################
#create ReduceVerts property for all objects
bpy.types.Object.ReduceVerts = bpy.props.FloatProperty(
    name = "ReduceVerts", 
    default =99.7, 
    min = 0.0, 
    max = 99.9, 
    precision = 1, 
    description = "Reduce vertices by percent", 
    subtype = "PERCENTAGE"
)

# create OffLoad property
bpy.types.Scene.OffLoad = bpy.props.BoolProperty(
    name = "OffLoad", 
    default=True, 
    description='Store collection data in separate library file'
)

#########################################################################################################################################
#actions on pressing render or pre/ post render buttons
#render handlers don't work yet when using preview render for final render it's is not necessary to press pre render button

#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")]


#prepare for render sets all source objects to be visible in viewport and their display type to bounds
#all proxy objects will be hidden in viewport
#not working as desired
@persistent
def ProxyPreRenderHandler(scene):
    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
        

#cleanup after render sets all source objects back to be vhiden in viewport and their display type to solid
#all proxy objects will be set visible in viewport
#not working as desired
@persistent
def ProxyPostRenderHandler(scene):           
    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'}


#########################################################################################################################################      
#class to create vertex cloud representation        
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 context.object.select_get() and context.object.type == 'MESH' or 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" snippet is missing for now

        #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.active_object.ReduceVerts)
        bpy.ops.mesh.delete(type='VERT')
        bpy.ops.object.mode_set(mode='OBJECT')

        #clear all material slots from proxy object
        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 = "//proxy_library.blend"
            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"}

    
#########################################################################################################################################   
#draw 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):
        layout = self.layout

        #create label
        layout.label(text = "Convert Object to Vertex Cloud")
        
        #create slider with ReduceVerts property
        layout.prop(bpy.context.active_object, "ReduceVerts", text = "Reduce Vertices by:")
        
        #create checkbox for off load geometry
        row = layout.row()
        row.prop(bpy.context.scene, "OffLoad", text = "Offload Geometry")
        
        #create conversion button
        layout.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)
        #create post render button
        row.operator(OBJECT_OT_ProxyPostRender.bl_idname)

       
#########################################################################################################################################
#all classes that has to be registered/ unregistered  
classes = (
    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)

def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)

if __name__ == "__main__":
    register()