"""Name-US: CV-Paint Fracture
Description-US: Interactively Voronoi Fracture the selected object with the Hair System's Add Guides tool.

To Do
-----

- [ ] Find a way to paint strokes at a depth.
- [X] Improve Undo behavior in instances where there is an early return.

__version__ = "1.0.1"
__author__ = "Donovan Keith"
__copyright__ = "Copyright 2017, Maxon Computer Inc."
__email__ = "info@cineversity.com"
__status__ = "Development"
"""

import c4d
from c4d import gui

ID_VORONOI_FRACTURE = 1036557
ID_HAIR = 1017305

def tool():
    return c4d.plugins.FindPlugin(doc.GetAction(), c4d.PLUGINTYPE_TOOL)

def get_voronoi_and_object(op):
    """Returns voronoi, obj based on op.
    Assumes `op` is the child of a `voronoi` fracture. If its a voronoi fracture, `obj` is the first child.
    If `op` is naked, it generates a new voronoi fracture.

    call like:
    obj, voronoi = get_voronoi_and_object

    Can return None, None if they aren't present.

    Assumes that StartUndo() and EndUndo() will be handled by the calling function.
    """

    obj = None
    voronoi = None

    # Did the user select a Voronoi Object?
    if op.IsInstanceOf(ID_VORONOI_FRACTURE):
        voronoi = op
        obj = op.GetDown()

        # Is the direct child a Connector Object? You probably want the one after that.
        CONNECTOR_ID = 180000011
        if obj and obj.IsInstanceOf(CONNECTOR_ID):
            obj = obj.GetNext()

    # Or is it a regular object?
    else:
        # See if the parent is a voronoi fracture and use that.
        parent = op.GetUp()
        if parent and parent.IsInstanceOf(ID_VORONOI_FRACTURE):
            voronoi = parent
            obj = op
        # Otherwise make a Voronoi Fracture object
        elif (not parent) or (not parent.IsInstanceOf(ID_VORONOI_FRACTURE)):
            voronoi = c4d.VoronoiFracture()
            doc.InsertObject(voronoi, pred=op)
            doc.AddUndo(c4d.UNDOTYPE_NEW, voronoi)

            op_mg = op.GetMg()

            # Possibly compatible (it's got kids, or is a generator), shove it under a Connect Object and hope for the best.
            cache = op.GetCache()
            if op.GetDown() or (cache and cache.GetDown()):
                CONNECT_ID = 1011010
                connect_obj = c4d.BaseObject(CONNECT_ID)
                connect_obj[c4d.CONNECTOBJECT_WELD] = False  # Don't weld the points as that will just slow us down.
                connect_obj.InsertUnder(voronoi)
                connect_obj.SetMg(op_mg)
                doc.AddUndo(c4d.UNDOTYPE_NEW, connect_obj)
                doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
                op.Remove()
                op.InsertUnder(connect_obj)
                op.SetMg(op_mg)
                obj = connect_obj
            
            # A Single Poly Object or Poly Object Generator is Selected
            elif op.IsInstanceOf(c4d.Opolygon) or (cache and cache.IsInstanceOf(c4d.Opolygon)):
                doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
                op.Remove()
                op.InsertUnder(voronoi)
                op.SetMg(op_mg)
                obj = op
            # Not compatible. Maybe a Null?
            else:
                voronoi.Remove()
                return None, None

    return voronoi, obj

def fracture_object(op):
    """Adds a Hair object as a source in the selected fracture object

    Moved to its own method to more gracefully handled Start/End Undo.
    Assumes that StartUndo() and EndUndo() will be handled by the calling function.
    """

    if not op:
        return

    # Retrieve the Voronoi Fracture object and the object to be fractured.
    voronoi, obj = get_voronoi_and_object(op)
    if (not voronoi) or (not obj):
        c4d.gui.MessageDialog("Unable to Fracture object. Ensure a single Polygon object is selected and run this command again.")
        return

    # Create a Hair Object
    hair = c4d.BaseObject(ID_HAIR)
    if (hair is None):
        return

    hair[c4d.HAIRSTYLE_LINK] = obj
    hair[c4d.HAIRSTYLE_SEGMENTS] = 1
    hair[c4d.HAIRSTYLE_ROOT_PLACEMENT] = 14  # Custom
    hair[c4d.HAIRSTYLE_HAIR_COUNT] = 0
    hair[c4d.HAIRSTYLE_HAIR_SEGMENTS] = 1
    hair[c4d.HAIRSTYLE_GENERATE] = 2  # Flat

    # Add hair object to scene
    hair.SetName(voronoi.GetName() + " Source")
    doc.InsertObject(hair, pred=voronoi)
    doc.AddUndo(c4d.UNDOTYPE_NEW, hair)

    # Add hair to voronoi's sources
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, voronoi)
    voronoi.AddSceneObject(hair)

    # Setup scene for editing
    doc.SetActiveObject(hair)
    c4d.CallCommand(1017640, 1017640) # Add Guides
    tool()[c4d.HAIR_TOOL_ADDROOT_COUNT]=2
    tool()[c4d.HAIR_TOOL_ADDROOT_LENGTH]=0

def main():
    c4d.StopAllThreads()
    
    if not op:
        c4d.gui.MessageDialog("Please select one Polygon object you would like to fracture and run this command again.")
        return

    doc.StartUndo()

    fracture_object(op)

    doc.EndUndo()
    c4d.EventAdd()

if __name__=='__main__':
    main()
