## KickerAngle.py Version 1.01

## Copyright (c) 2007 Bruce Vaughan, BV Detailing & Design, Inc.

## All rights reserved.

## NOT FOR SALE. The software is provided "as is" without any warranty.

################################################################################

""" This script will add a miscellaneous 'Angle' rolled shape kicker and kicker

    gusset plates to a W flange or Tube beam by picking points while in plan.

    The kicker and plates will be oriented vertically.

    Thickness reference point on the plate material is "Center".

    A pin hole and bolt can be added to the plate material and kicker at the

    high end or both ends. The gage on the angle will be the leg width/2.

    The user selects a series of point pairs (one on each supporting member)

    and identifies which end is at the beam bottom flange (1st or 2nd point).

    **NOTE: Point 1 must be on Member 1 and Point 2 must be on Member 2.**

    **USAGE NOTE: The pin hole gages on the gusset plates and kicker edge

        distance at pin are used to calculate the working point for the

        kicker angle even though pin holes may not be installed.       

 

    Version 1.00 (3/18/07) -  Initial release

    Version 1.01 (3/21/07) -  Correction to 'theta' calculation

   

    ************************************************************

    ****NOTE: This script can only be executed in plan**********

    ****NOTE: This version is not compatible with SDS/2 6.3.****

    ************************************************************

 

    Developed in SDS/2 version 7.035 or later and requires version 7.023 or higher.

    Developed by Bruce Vaughan, BV Detailing & Design, Inc. (BVD)  (615) 646-9239

    For comments, suggestions or questions call BV at the number above, email

    bvaughan@bvdetailing or post to SDS/2 forum.

   

"""

def run_script():

    import os

    # startup code begin

    from math import cos, floor, pi, tan, sin, atan

    from param import yes_or_no, ResponseNotOK, Units, ClearSelection

    from param import Dialog, dim_print, Warning, dim

    from macrolib.FileDefaults import import_data, export_data

    # macrolib.pickle required

    from macrolib.MemSelection import mem_select

    # macrolib.MemCnt required

    from macrolib.Weld import mtrl_weld

    from macrolib.ExceptWarn import formatExceptionInfo

    from macrolib.angle import rtod, dtor, angle_between_mem_pts, planRotPts, rotation_test

    from macrolib.kicker import kicker_pl, gpl_hole, add_kicker_pl, add_kicker

    from macrolib.PolarPt import PolarPt3D

    from macrolib.Basis3D import BasisTransToGlobal, BasisTransToLocal

    from macrolib.P3D import Plane3D

    from macrolib.round_length import round_length_next

    from macrolib.PrintPtList import formatPtList

    from macrolib.bolt_match import bolt_match

 

    Units("feet")

    from shape import Shape

    from point import Point, PointLocate

    from member import Member, MemberLocate

    from mtrl_list import MtrlLocate, HoleLocate

    from rect_plate import RectPlate

    from rolled_section import RolledSection

    from hole_add import Hole

    from bolt_add import Bolt

    from mtrl_fit import MtrlFit

    from job import Job

    from fab import Fabricator

    from mtrl_cut import MtrlCut

    # startup code end

    ## Variables Section ################################################

    # system path for defaults file

    default_file_path = os.path.join(os.getcwd(), "macro", "Defaults")

    # defaults file

    def_file = "KickerAngle.txt"

 

    # Dialog box image path and file name

    image_path = os.path.join(os.getcwd(), "macro", "Images")

 

    # Dialog box image path and file name

    image_name1 = os.path.join(image_path, "KickerAngle1.gif")

    image_name2 = os.path.join(image_path, "KickerAngle2.gif")

    image_name3 = os.path.join(image_path, "KickerAngle3.gif")

    image_name4 = os.path.join(image_path, "KickerAngle4.gif")

 

    # Enable or disable import/export dialog dictionary variables

    # ["Enable" "Disable"]

    enable_default_import_export = "Enable"

 

    finishList = ["None", "Red Oxide", "Yellow Zinc", "Gray Oxide", "Sandblasted", "Blued Steel", "Galvanized"]

    holeTypeList = ["Standard Round", "Short Slot", "Oversized Round", "Long Slot"]

    boltTypeList = Job().bolt_sched()

    mtrlGradeList = Job().steel_grades("Plate").keys()

    weldSizeList = ['3/16', '1/4', '5/16']

    pinList = ("High End", "Both Ends", "None")

    rndList = ('0', '1/16', '1/8', '1/4', '1/2', '1')

 

    ## Defaults Section #############################################

    # ('NS', 'FS')

    which_sidePlate = 'NS'

    # ('Point 1', 'Point 2')

    whichIsHigh = ('Point 2')

    # plate offset from the selected point locations

    points_x_offset = -0.1875

    # plate rotation from the orthogonal

    theta = 0.0

    # side(s) for gusset plates ("BS", "NS", "FS")

    which_side = "BS"

    # add plate at top or bottom flange ("Top", "Bott", "T&B")

    top_or_bott = "Bott"

    # plate width (width is vertical)

    gpl_width = 5.0

    # plate length (length is horizontal)

    gpl_length = 5.0

    # plate thickness

    gpl_thk = 0.375

    # plate clip dimension

    gpl_clip = 1.0

    # set plates normal to beam or vertical ("Normal", "Vertical")

    norm_or_vert = "Normal"

 

    matlcolor = "Yellow Zinc"

 

    matlgrade = "A36"

    kickmtrl = "L3x3x1/4"

    kickgrade = "A36"

    kick_ed = 1.5

    # round off kicker length to the next increment

    # ('0', '1/16', '1/8', '1/4', '1/2', '1')

    rnd_inc = '1/2'

    # "User" "System" Create user piece mark or system piece mark

    mark_system_user = "User"            

    user_piece_mark = "K_01"

 

    # add hole for erection bolt ("High End", "Both Ends", "None")

    pin_hole = "High End"

 

    # horizontal gage, distance from plate edge

    x_ga = 3.0

    # vertical gage, distance from plate edge

    y_ga = 3.0

    bolt_size = 0.75

    bolt_type = "A325"

    hole_type = "Standard Round"

    # long slot length where applicable - calculate if = 0"

    long_slot_length = 0.0

 

    # add weld       ("Yes", "No")

    add_gpl_weld = "Yes"

 

    # weld type ("Fillet", "Square butt")

    gpl_weld_type = "Fillet"

    weld_size = "3/16"

    ## end Defaults and Variables Section ###############################

    #####################################################################

    ## Import defaults data if enabled

    if enable_default_import_export == "Enable":

        dd0 = import_data(os.path.join(default_file_path, def_file))

        if dd0:

            for key, value in dd0.items():

                exec "%s = %s" % (key, repr(value)) in None

    #####################################################################

    ## Main program loop

    # Select member

    while True:

        # Select two members

        bm_list1 = mem_select("Select a WF or HSS BEAM member", ['Beam',], ["W flange", "Tube"], True, False)

        if bm_list1 is None:

            break

        mem1 = bm_list1[0]

       

        bm_list2 = mem_select("Select another WF or HSS BEAM member", ['Beam',], ["W flange", "Tube"], True, False)

        if bm_list2 is None:

            break

        mem2 = bm_list2[0]       

 

        #############################################################

        ## DIALOG BOX 1 --------------------------------------------#

        #############################################################

        dlg1 = Dialog("Add kicker gusset plates")

        dlg1.menu("print_doc", ("Yes", "No"), "No", "Print documentation only")

        dlg1.tabset_begin()

        dlg1.tab("General Information")

        dlg1.group_title("Kicker point pairs will be selected after dialog box")

        dlg1.menu('which_sidePlate', ('NS', 'FS'), which_sidePlate, 'Which side of kicker plates                              ')

        dlg1.menu('whichIsHigh', ('Point 1', 'Point 2'), whichIsHigh, 'Which POINT is HIGH')

        dlg1.entry("points_x_offset", dim_print(points_x_offset), "Member 'X' offset distance from selected points to PL CL")

        dlg1.menu('rnd_inc', rndList, rnd_inc, 'Round off length to the next increment')

        dlg1.group_title_end()

       

        dlg1.group_title("Kicker material")

        dlg1.entry("kickmtrl", kickmtrl, 'Kicker material       ')

        dlg1.menu("mark_system_user", ("User", "System"), mark_system_user, "User or System Piece Mark")

        dlg1.entry("user_piece_mark", user_piece_mark, "User Piece Mark")

        dlg1.entry("kick_ed" , dim_print(kick_ed), 'Edge distance at pin')

        dlg1.menu("kickgrade", Job().steel_grades("Angle").keys(), matlgrade, "Material grade")

        dlg1.group_title_end()

       

        dlg1.group_title("Kicker Plate Material")

        dlg1.entry("gpl_width", dim_print(gpl_width), "Plate width (vertical)")

        dlg1.entry("gpl_length", dim_print(gpl_length), "Plate length (horizontal)")

        dlg1.entry("gpl_thk", dim_print(gpl_thk), "Plate thickness")

        dlg1.entry("gpl_clip", dim_print(gpl_clip), "Clip dimension")

        dlg1.menu("matlcolor", finishList, matlcolor, "Material finish" )

        dlg1.menu("matlgrade", Job().steel_grades("Plate").keys(), matlgrade, "Material grade")

        dlg1.group_title_end()

       

        dlg1.group_title("Additional Options")

        dlg1.menu("pin_hole", pinList, pin_hole, "Add a pin hole and bolt")

        dlg1.menu("add_gpl_weld", ("Yes", "No"), add_gpl_weld, "Add weld" )

        dlg1.group_title_end()

 

        dlg1.tab("Pin Hole Information")

        dlg1.group_title("Dimensions")

        dlg1.entry("x_ga", dim_print(x_ga), "Horizontal gage from edge of plate")

        dlg1.entry("y_ga", dim_print(y_ga), "Vertical gage from edge of plate")

        dlg1.group_title_end()

       

        dlg1.group_title("Bolt Information")

        dlg1.entry("bolt_size", dim_print(bolt_size), "Enter bolt size")

        dlg1.menu("bolt_type", Job().bolt_sched(), bolt_type, "Bolt type in plate")

        dlg1.group_title_end()

       

        dlg1.group_title("Hole Information")

        dlg1.menu("hole_type", holeTypeList, hole_type, "Hole type in plate")

        dlg1.group_title_end()

       

        dlg1.group_title("'long slot length' = 0 to calculate length")

        dlg1.entry("long_slot_length", dim_print(long_slot_length), "Slot length (applies to 'Long Slot')")

        dlg1.group_title_end()

        dlg1.group_title("Graphic Image")

        dlg1.image(image_name3)

        dlg1.group_title_end

 

        dlg1.tab("Weld Information")

        dlg1.group_title("Welding Data")

        dlg1.menu("gpl_weld_type", ("Fillet", "Square butt", "Bevel groove"), gpl_weld_type, "Weld type                         " )

        dlg1.menu("weld_size", ("3/16", "1/4", "5/16"), weld_size, "Weld size")

        dlg1.group_title_end()

        dlg1.group_title("Graphic Image")

        dlg1.image(image_name4)

        dlg1.group_title_end

       

        dlg1.tab("Graphic Image")

        #dlg1.column_group_begin()

        #dlg1.column(0)

        dlg1.group_title("'X' Kickers - 2 Loops required")

        dlg1.image(image_name1)

        dlg1.group_title_end()

        #dlg1.column(0)

        dlg1.group_title("Kicker skewed with respect to beams")

        dlg1.image(image_name2)

        dlg1.group_title_end()

        #dlg1.column_group_end()

        dlg1.tabset_end()

        try:

            dd1 = dlg1.done()

        except ResponseNotOK:

            break 

       

        # Update the local namespace for next loop

        for key, value in dd1.items():

            exec "%s = %s" % (key, repr(value)) in None

        ###############################################################

        ## END DIALOG BOX 1 ------------------------------------------#

        ###############################################################

        if print_doc == "Yes":

            print __doc__

            break

        ##############################################################

        # Export defaults to disk if enabled

        if enable_default_import_export == "Enable":

            export_data(os.path.join(default_file_path, def_file), dd1)

        ###############################################################

 

        pt_list = [] 

        while True:

            pt1 = PointLocate("Select Point 1 or return")

            if pt1 is None:

                break

            pt2 = PointLocate("Select Point 2")

            if pt2 == None:

                break

            else:

                pt_list.append([pt1, pt2])

 

        ## Prepare to add gusset plates #########

        dd1['norm_or_vert'] = 'Vertical'

 

        # If beam is a Tube, clipped corners are not required   

        if mem1.mtrl_type == "Tube":

            dd1['gpl_clip'] = 0.0

       

        ## Add gusset 1 #########################

        # Determine side and T or B member 1

        if whichIsHigh == 'Point 1':

            dd1['top_or_bott'] = 'Top'

        else:

            dd1['top_or_bott'] = 'Bott'

 

        if pin_hole == "High End" and dd1['top_or_bott'] == 'Top':

            dd1['add_pin_hole'] = 'Yes'

        elif pin_hole == "Both Ends":

            dd1['add_pin_hole'] = 'Yes'

        else:

            dd1['add_pin_hole'] = 'No'

       

        rp_list1 = []           

        for t in pt_list:

            pt = mem1.trans_to_local(t[1] - mem1.left.location)

            if pt.z > 0:

                dd1['which_side'] = 'NS'

            else:

                dd1['which_side'] = 'FS'

            # Calculate theta

            dd1['theta'] = rtod(atan(tan(angle_between_mem_pts(mem1, t[0], t[1])) / cos(dtor(mem1.slope))))

            rp_list1 += add_kicker_pl([t[0], ], [mem1, ], dd1)

 

        ## Add gusset 2 #########################

        # Determine side and T or B member 2

        if whichIsHigh == 'Point 1':

            dd1['top_or_bott'] = 'Bott'

        else:

            dd1['top_or_bott'] = 'Top'

       

        if pin_hole == "High End" and dd1['top_or_bott'] == 'Top':

            dd1['add_pin_hole'] = 'Yes'

        elif pin_hole == "Both Ends":

            dd1['add_pin_hole'] = 'Yes'

        else:

            dd1['add_pin_hole'] = 'No'

 

        rp_list2 = []           

        for t in pt_list:

            pt = mem2.trans_to_local(t[0] - mem2.left.location)

            if pt.z > 0:

                dd1['which_side'] = 'NS'

            else:

                dd1['which_side'] = 'FS'

            # Calculate theta

            dd1['theta'] = rtod(atan(tan(angle_between_mem_pts(mem2, t[0], t[1]))) * cos(dtor(mem1.slope)))

            rp_list2 += add_kicker_pl([t[1], ], [mem2, ], dd1)

 

        if mark_system_user == "System":

            mk = ""

        else:

            mk = user_piece_mark

 

        for rp1, rp2 in zip(rp_list1, rp_list2):

            if whichIsHigh == 'Point 1':

                high = 1

            else:

                high = -1

           

            ptWP1 = rp1.pt1 + rp1.trans_to_global(x_ga, -y_ga, 0.0)

            ptWP2 = rp2.pt1 + rp2.trans_to_global(x_ga, -y_ga, 0.0)

           

            localBasis = BasisTransToGlobal(ptWP1, ptWP2, rp1.pt1 + rp1.trans_to_global(gpl_length, 0.0, 0.0))

           

            leg_ga = max(Shape(kickmtrl).depth/2, 1.375)

            pt_to_pt = ptWP1.dist(ptWP2)

            kick_len = round_length_next(pt_to_pt + kick_ed*2, rnd_inc)

            end_dist = (kick_len-pt_to_pt)/2

            if which_sidePlate == 'NS':

                side = 1

                leg_dir = 'In'

                rot = (90.0, 0.0, 0.0)

            else:

                side = -1

                leg_dir = 'Out'

                rot = (90.0, 0.0, 0.0)

 

            # print formatPtList('Work points', [ptWP1, ptWP2])

            memPt1 = ptWP1 + localBasis.translate(-end_dist, high*leg_ga, high*side*gpl_thk/2)

            memPt2 = ptWP2 + localBasis.translate(end_dist, high*leg_ga, high*side*gpl_thk/2)

            # print formatPtList('Calculated points', [memPt1, memPt2])

            # print formatPtList('Difference between points', [memPt1-ptWP1, memPt2-ptWP2])

            if not rotation_test(planRotPts(memPt1, memPt2)):

                rot = (rot[0], 0.0, 180.0)           

           

            angle1 = add_kicker(memPt1, memPt2, kickmtrl, kickgrade, mk, 'HZ.', leg_dir, rot)

            if pin_hole == "High End" and whichIsHigh == 'Point 1':

                gpl_hole(angle1, ptWP1, 'Web NS', 0.0, 0.0, bolt_size, 0.0, 'Standard Round', bolt_type)

                bolt_match(angle1.main_mtrl(), [rp1, ])

            elif pin_hole == "High End" and whichIsHigh == 'Point 2':

                gpl_hole(angle1, ptWP2, 'Web NS', 0.0, 0.0, bolt_size, 0.0, 'Standard Round', bolt_type)

                bolt_match(angle1.main_mtrl(), [rp2, ])

            elif pin_hole == "Both Ends":

                gpl_hole(angle1, ptWP1, 'Web NS', 0.0, 0.0, bolt_size, 0.0, 'Standard Round', bolt_type)

                gpl_hole(angle1, ptWP2, 'Web NS', 0.0, 0.0, bolt_size, 0.0, 'Standard Round', bolt_type)

                bolt_match(angle1.main_mtrl(), [rp1, rp2])

                       

        if not yes_or_no("Add kicker gusset plates to another member?"):

            break

 

## End run_script()

if __name__ == '__main__':

    try:

        run_script()

    finally:

        del run_script