################################################################################
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This is free software; you may redistribute it, and/or modify it,
# under the terms of the GNU General Public License.
#
# 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 (http://www.gnu.org/licenses/) for more details.
#
# ***** END GPL LICENSE BLOCK *****
'''
Create "Beam" primitives. Based on original script by revolt_randy.
'''
# Author(s): revolt_randy, Jambay
#
# @todo: track 3D cursor for location.
#
################################################################################

import bpy
from bpy.props import BoolProperty, EnumProperty, FloatProperty, FloatVectorProperty, IntProperty
from bpy_extras import object_utils


########################################
#
# Create vertices for end of mesh
#
# y_off - verts y-axis origin
#
# returns:
#  endVs - x,y,z list
#
def beamEndVs(sRef, y_off):
    thick = sRef.beamW * 2

    if sRef.Type == '2':  # swap width and height for C shape
        bEndX2 = sRef.beamZ / 2
        bEndXInr = ((sRef.beamZ - thick) / 2)
        bEndZ2 = sRef.beamX / 2
        bEndZInr = ((sRef.beamX - thick) / 2)
    else:
        bEndX2 = sRef.beamX / 2
        bEndXInr = ((sRef.beamX - thick) / 2)
        bEndZ2 = sRef.beamZ / 2
        bEndZInr = ((sRef.beamZ - thick) / 2)

    endVs = []

    # outer ...
    endVs.append((bEndX2, y_off, bEndZ2))
    endVs.append((-bEndX2, y_off, bEndZ2))
    endVs.append((-bEndX2, y_off, -bEndZ2))
    endVs.append((bEndX2, y_off, -bEndZ2))
    # innner ...
    endVs.append((bEndXInr, y_off, bEndZInr))
    endVs.append((-bEndXInr, y_off, bEndZInr))
    endVs.append((-bEndXInr, y_off, -bEndZInr))
    endVs.append((bEndXInr, y_off, -bEndZInr))

    return endVs


########################################
#
# Create End Faces
#
# verts_list - list of vertices
#
# returns:
#  beamFs, a list of tuples defining the end faces.
#
def beamEndFaces(verts_list):

    beamFs = []

    num_of_verts = int(len(verts_list) / 2)

    # Create list of faces
    for index in range(num_of_verts):
        faces_temp = []

        if index == (num_of_verts - 1):
            faces_temp.append(verts_list[index])
            faces_temp.append(verts_list[index - index])
            faces_temp.append(verts_list[index + 1])
            faces_temp.append(verts_list[index * 2 + 1])
        else:
            faces_temp.append(verts_list[index])
            faces_temp.append(verts_list[index + 1])
            faces_temp.append(verts_list[index + num_of_verts + 1])
            faces_temp.append(verts_list[index + num_of_verts])

        beamFs.append(tuple(faces_temp))

    return beamFs


########################################
#
# Bridge vertices to create side faces.
#
# front_verts - front face vertices
# back_verts - back face vertices
#  front & back must be ordered in same direction
#  with respect to y-axis
#
# returns:
#  sideFaces, a list of the bridged faces
#
def beamSides(front_verts, back_verts):
    sideFaces = []

    num_of_faces = (len(front_verts))

    # add first value to end of lists for looping
    front_verts.append(front_verts[0])
    back_verts.append(back_verts[0])

    # Build the faces
    for index in range(num_of_faces):
        facestemp = (front_verts[index], front_verts[index + 1], back_verts[index + 1], back_verts[index])
        sideFaces.append(facestemp)

    return sideFaces


####################################################
#
# Creates a box beam
#
# returns:
#  beamVs - x, y, z, location of each vertice
#  beamFs - vertices that make up each face
#
def create_beam(sRef):

    frontVs = []
    frontFs = []
    backVs = []

    y_off = sRef.beamY / 2  # offset from center for vertices

    frontVs = beamEndVs(sRef, y_off)
    backVs = beamEndVs(sRef, -y_off)

    # Combine vertices
    beamVs = frontVs + backVs

    # Create front face
    numofverts = len(frontVs)
    verts_front_list = []
    for index in range(numofverts):
        verts_front_list.append(index)

    frontFs = beamEndFaces(verts_front_list)

    # Create back face
    faces_back_temp = []
    verts_back_list = []

    numofverts = len(backVs)
    for index in range(numofverts):
        verts_back_list.append(index + numofverts)

    faces_back_temp = beamEndFaces(verts_back_list)

    # Create side faces
    faces_side_temp = []

    # Object has thickness, create list of outside vertices
    numofverts = len(verts_front_list)
    halfVerts = int(numofverts / 2)
    frontVs = verts_front_list[0:halfVerts]
    backVs = verts_back_list[0:halfVerts]

    faces_side_temp = beamSides(frontVs, backVs)

    # Create list of inside vertices
    frontVs = verts_front_list[halfVerts:numofverts]
    backVs = verts_back_list[halfVerts:numofverts]

    faces_side_temp += beamSides(frontVs, backVs)

    # Combine all faces
    beamFs = frontFs + faces_back_temp + faces_side_temp

    return beamVs, beamFs


########################################
#
# Taper/angle faces of beam.
#  inner vert toward outer vert
#  based on percentage of taper.
#
# returns:
#  adVert - the calculated vertex
#
def beamSlant(sRef, outV, inV):
    bTaper = 100 - sRef.edgeA

    # calcuate variance & adjust vertex
    deltaV = ((inV - outV) / 100)
    adVert = outV + (deltaV * bTaper)

    return adVert


########################################
#
# Modify location to shape beam.
#
# verts - tuples for one end of beam
#
# returns:
#  verts - modified tuples for beam shape.
#
def beamSquareEnds(sRef, verts):

    # match 5th & 6th z locations to 1st & 2nd

    vert_orig = verts[0]
    vert_temp = verts[4]
    vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
    verts[4] = (vert_x, vert_temp[1], vert_orig[2])

    vert_orig = verts[1]
    vert_temp = verts[5]
    vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
    verts[5] = (vert_x, vert_temp[1], vert_orig[2])

    return verts


########################################
#
# Create U shaped beam
#  Shared with C shape - see beamEndVs
#  for sizing and rotate in addBeamObj.
#
# returns:
#  beamVs - vertice x, y, z, locations
#  beamFs - face vertices
#
def create_u_beam(sRef):

    # offset vertices from center
    y_off = sRef.beamY / 2

    frontVtemp = []
    frontFtemp = []
    frontVlist = []

    backVtemp = []
    backFtemp = []
    backVlist = []

    sideFs = []

    frontVtemp = beamEndVs(sRef, y_off)  # Box beam
    frontVtemp = beamSquareEnds(sRef, frontVtemp)  # U shape

    backVtemp = beamEndVs(sRef, -y_off)
    backVtemp = beamSquareEnds(sRef, backVtemp)

    beamVs = frontVtemp + backVtemp

    # Create front face
    for index in range(len(frontVtemp)):  # Build vert list
        frontVlist.append(index)

    frontFtemp = beamEndFaces(frontVlist)
    frontFtemp = frontFtemp[1:4]  # Remove 1st face

    # Create back face
    numofverts = len(backVtemp)
    for index in range(numofverts):  # Build vertex list
        backVlist.append(index + numofverts)

    backFtemp = beamEndFaces(backVlist)
    backFtemp = backFtemp[1:4]  # Remove face

    # Create list vertices for outside faces
    numofverts = int(len(frontVlist))
    halfVerts = int(numofverts / 2)
    frontVtemp = frontVlist[0:halfVerts]
    backVtemp = backVlist[0:halfVerts]

    sideFs = beamSides(frontVtemp, backVtemp)
    sideFs = sideFs[1:]  # Remove face

    # Create inside verts
    frontVtemp = frontVlist[halfVerts:numofverts]
    backVtemp = backVlist[halfVerts:numofverts]

    sideFs += beamSides(frontVtemp, backVtemp)
    sideFs = sideFs[0:3] + sideFs[4:]  # Remove face

    # fill in faces
    sideFs.append((0, 4, 12, 8))
    sideFs.append((5, 1, 9, 13))

    beamFs = frontFtemp + backFtemp + sideFs  # Combine faces

    return beamVs, beamFs


###################################
#
# returns:
#  verts_final - x, y, z, location of each vertice
#  faces_final - vertices that make up each face
#
def create_L_beam(sRef):

    thick = sRef.beamW

    # offset vertices from center
    x_off = sRef.beamX / 2
    y_off = sRef.beamY / 2
    z_off = sRef.beamZ / 2

    # Create temporarylists to hold vertices locations
    verts_front_temp = []
    verts_back_temp = []

    # Create front vertices by calculation
    verts_front_temp = [(-x_off, -y_off, z_off),
                        (-(x_off - thick), -y_off, z_off),
                        (-(x_off - thick), -y_off, -(z_off - thick)),
                        (x_off, -y_off, -(z_off - thick)),
                        (x_off, -y_off, -z_off),
                        (-x_off, -y_off, -z_off)]

    # Adjust taper
    vert_outside = verts_front_temp[0]
    vert_inside = verts_front_temp[1]
    vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
    verts_front_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]

    vert_outside = verts_front_temp[4]
    vert_inside = verts_front_temp[3]
    vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
    verts_front_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]

    # Create back vertices by calculation
    verts_back_temp = [(-x_off, y_off, z_off),
                       (-(x_off - thick), y_off, z_off),
                       (-(x_off - thick), y_off, -(z_off - thick)),
                       (x_off, y_off, -(z_off - thick)),
                       (x_off, y_off, -z_off),
                       (-x_off, y_off, -z_off)]

    # Adjust taper
    vert_outside = verts_back_temp[0]
    vert_inside = verts_back_temp[1]
    vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
    verts_back_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]

    vert_outside = verts_back_temp[4]
    vert_inside = verts_back_temp[3]
    vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
    verts_back_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]

    verts_final = verts_front_temp + verts_back_temp

    # define end faces, only 4 so just coded
    faces_front_temp = []
    faces_back_temp = []
    faces_side_temp = []

    faces_front_temp = [(0, 1, 2, 5), (2, 3, 4, 5)]
    faces_back_temp = [(6, 7, 8, 11), (8, 9, 10, 11)]

    verts_front_list = []
    verts_back_list = []
    num_of_verts = len(verts_front_temp)

    # build lists of back and front verts for beamSides function
    for index in range(num_of_verts):
        verts_front_list.append(index)
    for index in range(num_of_verts):
        verts_back_list.append(index + 6)

    faces_side_temp = beamSides(verts_front_list, verts_back_list)

    faces_final = faces_front_temp + faces_back_temp + faces_side_temp

    return verts_final, faces_final


###################################
#
# returns:
#  verts_final - a list of tuples of the x, y, z, location of each vertice
#  faces_final - a list of tuples of the vertices that make up each face
#
def create_T_beam(sRef):

    thick = sRef.beamW

    # Get offset of vertices from center
    x_off = sRef.beamX / 2
    y_off = sRef.beamY / 2
    z_off = sRef.beamZ / 2
    thick_off = thick / 2

    # Create temporarylists to hold vertices locations
    verts_front_temp = []
    verts_back_temp = []

    # Create front vertices
    verts_front_temp = [(-x_off, -y_off, z_off),
                        (-thick_off, -y_off, z_off),
                        (thick_off, -y_off, z_off),
                        (x_off, -y_off, z_off),
                        (x_off, -y_off, z_off - thick),
                        (thick_off, -y_off, z_off - thick),
                        (thick_off, -y_off, -z_off),
                        (-thick_off, -y_off, -z_off),
                        (-thick_off, -y_off, z_off - thick),
                        (-x_off, -y_off, z_off - thick)]

    # Adjust taper
    vert_outside = verts_front_temp[0]
    vert_inside = verts_front_temp[9]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_front_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_front_temp[3]
    vert_inside = verts_front_temp[4]
    verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]

    # Adjust taper of bottom of beam, so 0 the center
    # now becomes vert_outside, and vert_inside is calculated
    # 1/2 way towards center
    vert_outside = (0, -y_off, -z_off)
    vert_inside = verts_front_temp[6]
    vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
    verts_front_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]

    vert_outside = (0, -y_off, -z_off)
    vert_inside = verts_front_temp[7]
    vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
    verts_front_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]

    # Create fack vertices by calculation
    verts_back_temp = [(-x_off, y_off, z_off),
                       (-thick_off, y_off, z_off),
                       (thick_off, y_off, z_off),
                       (x_off, y_off, z_off),
                       (x_off, y_off, z_off - thick),
                       (thick_off, y_off, z_off - thick),
                       (thick_off, y_off, -z_off),
                       (-thick_off, y_off, -z_off),
                       (-thick_off, y_off, z_off - thick),
                       (-x_off, y_off, z_off - thick)]

    # Adjust taper
    vert_outside = verts_back_temp[0]
    vert_inside = verts_back_temp[9]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_back_temp[3]
    vert_inside = verts_back_temp[4]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]

    # Adjust taper of bottom of beam, so 0 the center
    # now becomes vert_outside, and vert_inside is calculated
    # 1/2 way towards center
    vert_outside = (0, -y_off, -z_off)
    vert_inside = verts_back_temp[6]
    vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
    verts_back_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]

    vert_outside = (0, -y_off, -z_off)
    vert_inside = verts_back_temp[7]
    vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
    verts_back_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]

    verts_final = verts_front_temp + verts_back_temp

    # define end faces, only 8 so just coded
    faces_front_temp = []
    faces_back_temp = []
    faces_side_temp = []

    faces_front_temp = [(0, 1, 8, 9), (1, 2, 5, 8),
                        (2, 3, 4, 5), (5, 6, 7, 8)]

    faces_back_temp = [(10, 11, 18, 19), (11, 12, 15, 18),
                       (12, 13, 14, 15), (15, 16, 17, 18)]

    verts_front_list = []
    verts_back_list = []
    num_of_verts = len(verts_front_temp)

    # build lists of back and front verts for beamSides function
    for index in range(num_of_verts):
        verts_front_list.append(index)
    for index in range(num_of_verts):
        verts_back_list.append(index + 10)

    faces_side_temp = beamSides(verts_front_list, verts_back_list)

    faces_final = faces_front_temp + faces_back_temp + faces_side_temp

    return verts_final, faces_final


###################################
#
# returns:
#  verts_final - a list of tuples of the x, y, z, location of each vertice
#  faces_final - a list of tuples of the vertices that make up each face
#
def create_I_beam(sRef):

    thick = sRef.beamW

    # Get offset of vertices from center
    x_off = sRef.beamX / 2
    y_off = sRef.beamY / 2
    z_off = sRef.beamZ / 2
    thick_off = thick / 2

    # Create temporarylists to hold vertices locations
    verts_front_temp = []
    verts_back_temp = []

    # Create front vertices by calculation
    verts_front_temp = [(-x_off, -y_off, z_off),
                        (-thick_off, -y_off, z_off),
                        (thick_off, -y_off, z_off),
                        (x_off, -y_off, z_off),
                        (x_off, -y_off, z_off - thick),
                        (thick_off, -y_off, z_off - thick),
                        (thick_off, -y_off, -z_off + thick),
                        (x_off, -y_off, -z_off + thick),
                        (x_off, -y_off, -z_off),
                        (thick_off, -y_off, -z_off),
                        (-thick_off, -y_off, -z_off),
                        (-x_off, -y_off, -z_off),
                        (-x_off, -y_off, -z_off + thick),
                        (-thick_off, -y_off, -z_off + thick),
                        (-thick_off, -y_off, z_off - thick),
                        (-x_off, -y_off, z_off - thick)]

    # Adjust taper
    vert_outside = verts_front_temp[0]
    vert_inside = verts_front_temp[15]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_front_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_front_temp[3]
    vert_inside = verts_front_temp[4]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_front_temp[8]
    vert_inside = verts_front_temp[7]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_front_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_front_temp[11]
    vert_inside = verts_front_temp[12]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_front_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]

    # Create back vertices by calculation
    verts_back_temp = [(-x_off, y_off, z_off),
                       (-thick_off, y_off, z_off),
                       (thick_off, y_off, z_off),
                       (x_off, y_off, z_off),
                       (x_off, y_off, z_off - thick),
                       (thick_off, y_off, z_off - thick),
                       (thick_off, y_off, -z_off + thick),
                       (x_off, y_off, -z_off + thick),
                       (x_off, y_off, -z_off),
                       (thick_off, y_off, -z_off),
                       (-thick_off, y_off, -z_off),
                       (-x_off, y_off, -z_off),
                       (-x_off, y_off, -z_off + thick),
                       (-thick_off, y_off, -z_off + thick),
                       (-thick_off, y_off, z_off - thick),
                       (-x_off, y_off, z_off - thick)]

    # Adjust taper
    vert_outside = verts_back_temp[0]
    vert_inside = verts_back_temp[15]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_back_temp[3]
    vert_inside = verts_back_temp[4]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_back_temp[8]
    vert_inside = verts_back_temp[7]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]

    vert_outside = verts_back_temp[11]
    vert_inside = verts_back_temp[12]
    vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
    verts_back_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]

    verts_final = verts_front_temp + verts_back_temp

# define end faces, only 7 per end, so just coded
    faces_front_temp = []
    faces_back_temp = []
    faces_side_temp = []

    faces_front_temp = [(0, 1, 14, 15), (1, 2, 5, 14),
                        (2, 3, 4, 5), (6, 7, 8, 9),
                        (6, 9, 10, 13), (12, 13, 10, 11),
                        (5, 6, 13, 14)]

    faces_back_temp = [(16, 17, 30, 31), (17, 18, 21, 30),
                       (18, 19, 20, 21), (22, 23, 24, 25),
                       (22, 25, 26, 29), (28, 29, 26, 27),
                       (21, 22, 29, 30)]

    verts_front_list = []
    verts_back_list = []
    num_of_verts = len(verts_front_temp)

    # build lists of back and front verts for beamSides function
    for index in range(num_of_verts):
        verts_front_list.append(index)
    for index in range(num_of_verts):
        verts_back_list.append(index + 16)

    faces_side_temp = beamSides(verts_front_list, verts_back_list)

    faces_final = faces_front_temp + faces_back_temp + faces_side_temp

    return verts_final, faces_final


################################################################################
#
# Generate beam object.
#
def addBeamObj(sRef, context):
    verts = []
    faces = []

    # type of beam to add
    if sRef.Type == '0':
        verts, faces = create_beam(sRef)
    elif sRef.Type == '1':
        verts, faces = create_u_beam(sRef)
    elif sRef.Type == '2':
        verts, faces = create_u_beam(sRef)
    elif sRef.Type == '3':
        verts, faces = create_L_beam(sRef)
    elif sRef.Type == '4':
        verts, faces = create_I_beam(sRef)
    elif sRef.Type == '5':
        verts, faces = create_T_beam(sRef)
    else:  # unknown type, use default.
        verts, faces = create_beam(sRef)

    beamMesh = bpy.data.meshes.new("Beam")
    beamObj = bpy.data.objects.new("Beam", beamMesh)
    context.scene.objects.link(beamObj)
    context.scene.objects.active = beamObj
    beamObj.select = True

    beamMesh.from_pydata(verts, [], faces)
    beamMesh.update(calc_edges=True)

    if sRef.Type == '2':  # Rotate C shape
        bpy.ops.transform.rotate(value=1.570796, constraint_axis=[False, True, False])
        bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)


################################################################################
#
# Create a beam primitive.
#
#  UI functions and object creation.
#
class addBeam(bpy.types.Operator):
    bl_idname = "mesh.add_beam"
    bl_description = "Beam Builder"
    bl_label = "Beam Builder"
    bl_options = {'REGISTER', 'UNDO'}

    Type = EnumProperty(items=(
        ('0', "Box", "Square Beam"),
        ("1", "U", "U Beam"),
        ("2", "C", "C Beam"),
        ("3", "L", "L Beam"),
        ("4", "I", "T Beam"),
        ("5", "T", "I Beam")
    ),
        description="Beam form.")

    beamZ = FloatProperty(name="Height", min=0.01, max=100, default=1)
    beamX = FloatProperty(name="Width", min=0.01, max=100, default=.5)
    beamY = FloatProperty(name="Depth", min=0.01, max=100, default=2)
    beamW = FloatProperty(name="Thickness", min=0.01, max=1, default=0.1)

    edgeA = IntProperty(name="Taper", min=0, max=100, default=0, description="Angle beam edges.")

    ########################################
    def draw(self, context):
        layout = self.layout

        box = layout.box()
        row = box.row()
        row.prop(self, 'Type', text='')

        box.prop(self, 'beamZ')
        box.prop(self, 'beamX')
        box.prop(self, 'beamY')
        box.prop(self, 'beamW')

        if self.Type != '0':
            box.prop(self, 'edgeA')

    ########################################
    def execute(self, context):
        if bpy.context.mode == "OBJECT":
            addBeamObj(self, context)
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "Option only valid in Object mode")
            return {'CANCELLED'}
