#!BPY
"""
Name: 'spiro_fit'
Blender: 248
Group: 'Misc'
Tooltip: 'Build a rough spiral that fit the active object.'
"""

__author__ = "Antonio Ospite"
__url__ = []
__version__ = "0.1"

__bpydoc__ = """\
    Build a spiral that fit the active object.

    You should apply the Size and Rotations to the active object for better
    results.
"""


# ---------------------------------------------------------------------
#    Copyright (c) 2007 Antonio Ospite
#
#    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 St, Fifth Floor, Boston, MA  02110-1301  USA
#
# ---------------------------------------------------------------------
# TODO:
#   - a GUI?
#   - Use bezier curves?
#   - Add an offset to the curve start and a fixed max height?
#   - Add and option to skip the object mapping and build the spiral
#     around the bounding box.
#   - Support object types other than meshes?
#


import Blender
from Blender import Window, Scene, Object, Mesh, Curve, BezTriple
from Blender.Mathutils import *
from math import *


SPIRES = 3
SPIRE_RESOLUTION = 16


def midpoint(v1, v2):
    m = MidpointVecs(Vector(v1), Vector(v2))
    return [m[0], m[1], m[2]]


def distance(v1, v2):
    d = (Vector(v1)-Vector(v2)).length
    return d


def spiral_point(t, r, z_coord):
    """Parametric equation of a cylindrical spiral.

    See http://mathworld.wolfram.com/Helix.html for further details.
    """

    x = r * cos(SPIRES*t)
    y = r * sin(SPIRES*t)
    z = z_coord

    return [x, y, z]


def do_object_mapping(mesh, vert, center):
    """Map the vert on the object surface, the center is used for the
    mapping direction
    """

    intersections = []
    ray = Vector(vert)
    orig = Vector(center)

    dir = ray-orig

    # We search for intersection points
    for f in mesh.faces:

        if len(f) == 3:
            tri = Vector(f[0]), Vector(f[1]), Vector(f[2])
            ret = Intersect(tri[0], tri[1], tri[2], dir, orig, 1)
            if ret:
                intersections.append(ret)

        # If a quad we split it for the intersection test.
        elif len(f) == 4:
            tri = Vector(f[0]), Vector(f[1]), Vector(f[2])
            ret = Intersect(tri[0], tri[1], tri[2], dir, orig, 1)
            if ret:
                intersections.append(ret)
            else:
                tri = Vector(f[2]), Vector(f[3]), Vector(f[0])
                ret = Intersect(tri[0], tri[1], tri[2], dir, orig, 1)
                if ret:
                    intersections.append(ret)


    # Now choose the intersection point nearest to our vert
    if len(intersections) > 0:
        vmapped = min( [(distance(i, vert), i) for i in intersections] )[1]
    else:
        vmapped = orig

    return [ vmapped[0], vmapped[1], vmapped[2] ]


def build_spiro_curve(obj):
    """Build the fitting curve around the active object.

    For another example about how to use poly-curves, see the link:
    http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Freehand_Polyline_Draw_Tool
    """

    cu = Curve.New()

    mesh = obj.getData()
    bb = obj.getBoundBox()

    bb_xmin = min( [ v[0] for v in bb ] )
    bb_ymin = min( [ v[1] for v in bb ] )
    bb_zmin = min( [ v[2] for v in bb ] )

    bb_xmax = max( [ v[0] for v in bb ] )
    bb_ymax = max( [ v[1] for v in bb ] )
    bb_zmax = max( [ v[2] for v in bb ] )

    radius = distance([bb_xmax, bb_ymax, bb_zmin], [bb_xmin, bb_ymin, bb_zmin])/2.0

    height = bb_zmax - bb_zmin

    cx = (bb_xmax + bb_xmin)/2.0
    cy = (bb_ymax + bb_ymin)/2.0
    center = [ cx, cy, bb_zmin]

    # Start building the spiral
    cp =  spiral_point(bb_zmin, radius, bb_zmin)
    cp = do_object_mapping(mesh, cp, center)

    spiral = cu.appendNurb(cp + [1, 1])
    spiral.type = 0

    steps = SPIRES * SPIRE_RESOLUTION
    for i in range(1, steps+1):

        t = bb_zmin + (2*pi/steps)*i
        z = bb_zmin + (float(height)/steps)*i
        center = [cx, cy, z]

        cp = spiral_point(t, radius, z)
        cp = do_object_mapping(mesh, cp, center)

        spiral.append(cp + [1])

    cu.update()

    return cu


def spirofit():
    """Get the active object and spirofit-it :)
    """

    editmode = Window.EditMode()
    if editmode: Window.EditMode(0)

    scene = Scene.GetCurrent()
    camObj = scene.objects.camera

    # get the current Object
    object = scene.objects.active

    if (object.getType() != "Mesh"):
        print "Type:", object.getType(), "\tSorry, only mesh Object supported!"
        return

    # Build the siral around the object
    spiro_curve = build_spiro_curve(object)

    # Link the object to the scene
    spiro_ob = Blender.Object.New ('Curve', 'spirofitted_'+object.getName())
    spiro_ob.link(spiro_curve)
    spiro_ob.setSize(1.1, 1.1, 1)

    scene.link(spiro_ob)
    scene.update()


    # Mesh.Mode(oldmode)
    if editmode: Window.EditMode(1)
    Blender.Redraw()


# The main program
if __name__ == "__main__":
    spirofit()
