## EllipseLayout.py Version 1.00

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

##  All rights reserved.

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

"""

    Revision History:

        Version 1.00 (11/12/06) - Development version

        Version 1.01 (11/12/06) - Removed print statements used for debugging

                                  Removed 'z' adjustment factor since 3D translations are made using

                                  material coordinates instead of member coordinates

                   

    Select three points in your current plane in a counter-clockwise direction:

        1. The ellipse center point (Point 1)

        2. A point representing an axis end point in the ellipse 'x' direction (Point 2)

            From this point the 'x' semi-axis default dimension and default ellipse rotation is calculated.

        3. Any point in the current plane that is not collinear with the other two (Point 3)

            From this point the ellipse 'y' semi-axis default dimension is calculated.

            The third point should be picked counter-clockwise with respect to the other two points.

            Three non-collinear points are required to define a plane.

 

    Dialog box selections:

        1. The quadrant to layout the ellipse (one quadrant is laid out at a time)

        2. Number of layout spaces between points (ellipse resolution)

        3. Axis dimensions and rotation

 

    Algorithim:

        Add a miscellaneous member representing the unit vector of the defined plane (WP)

        Divide the quadrant (90 degrees) by resolution

        Resolution rays originate at member WP (0,0,0) (Point 1)

        Calculate member WP -z (ellipse x) and y (ellipse y) coordinates of the intersection of each

            resolution ray with the ellipse

        Rotate each calculated point about member WP by the rotation angle of the ellipse

        Add miscellaneous member at each rotated resolution point (RP)

       

"""

def run_script():

    import macrolib.pickle

    from macrolib.FileDefaults import import_data, export_data, data_Defaults_path

    from macrolib.angle import rtod, dtor

    from macrolib.P3D import Plane3D

    from macrolib.PointRotate import PointRotate3D

   

    from math import atan2, tan, cos, sin, pi

    from param import yes_or_no, ResponseNotOK, Units, Dialog, dim_print, Warning, dim

    import os

    import sys

    Units("feet")

    from point import Point, PointLocate

    from member import Member

    from cons_line import ConsLine

    # from rnd_bar import RndBar

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

    ## Variables Section

    # system path for default values files

    # auto save path

    default_file_path = data_Defaults_path()

    # default values file name

    def_file = "EllipseLayout_v1_01.txt"

    # default to enable or disable the automatic importing and exporting of dialog dictionary variables

    # ("Enable", "Disable")

    enable_default_import_export = "Enable"

 

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

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

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

 

    ## Defaults Section

    resEllipse = 6

    quadEllipse = "0-90"        # ("0-90", "90-180", "180-270", "270-360")

    """

    # Default values are calculated from user input:

    axis1Ellipse

    axis2Ellipse

    rotEllipse

    """

 

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

    def add_RP(pt1, pt2, mk=""):

        # cannot assign existing = "Yes"

        # cannot assign sequence to miscellaneous member

        # description is ignored

        memadd1 = Member('Misc Round Bar')

        memadd1.piecemark = mk

        memadd1.left.location = pt1

        memadd1.right.location = pt2

        memadd1.grade = "A36"

        memadd1.centered = "Yes"

        memadd1.bar_diameter = 1

        memadd1.work_pt_dist = 1.0

        memadd1.mtrl_type = "Round bar"

        memadd1.finish = "None"

        # memadd1.existing = "Yes"

        memadd1.description = "Ref Point"

        memadd1.ref_pt_offset = (0, 0, 0)

        memadd1.add()

        return memadd1

       

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

    while 1:

        # Auto import default values file       

        if enable_default_import_export == "Enable":

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

            if dd1:

                for key, value in dd1.items():

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

            else:

                Warning("Invalid data or defaults file does not exist - Reverting to original defaults")

                   

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

        ptWP = PointLocate ("Pick the ellipse center point                                   ")

        ptAxis = PointLocate("Pick an ellipse 'X' direction axis WP")

        ptPlane = PointLocate("Pick another point counter-clockwise to define the current plane")

        if None not in (ptWP, ptAxis, ptPlane):

            # create class instance 'a'

            a = Plane3D(ptWP, ptAxis, ptPlane)

            # add ellipse center WP

            mem1 = add_RP(ptWP, ptWP + a.N_uv, "WP")

           

            # calculate rotation of ellipse with respect to mem1.main_mtrl()

            ptTem = mem1.main_mtrl().to_local(ptAxis - ptWP)

            if round(ptTem.z, 4) == 0.0:

                if ptTem.y > 0.0:

                    rotEllipse = pi/2

                else:

                    rotEllipse = -pi/2

            else:               

                rotEllipse = atan2(ptTem.y, -ptTem.z)

 

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

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

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

            dlg1 = Dialog("Ellipse Layout")

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

            dlg1.tabset_begin()

            dlg1.tab("General Information")

            dlg1.group_title("Additional Ellipse Information")

            dlg1.entry("axis1Ellipse", dim_print(ptWP.dist(ptAxis)), "Ellipse semi-axis dimension 'X'                       ")

            dlg1.entry("axis2Ellipse", dim_print(ptWP.dist(ptPlane)), "Ellipse semi-axis dimension 'Y'")

            dlg1.entry("resEllipse", resEllipse, "Ellipse resolution")

            dlg1.menu("quadEllipse", ("0-90", "90-180", "180-270", "270-360"), quadEllipse, "Ellipse quadrant")

            dlg1.entry("rotEllipse", rtod(rotEllipse), "Ellipse rotation with respect to current plane")

            dlg1.tab("Image 1")

            dlg1.group_title("Ellipse laid out in plane of beam top flange")

            dlg1.image(image_name1)

            dlg1.tab("Image 2")

            dlg1.group_title("Five 3 point construction circles approximate the ellipse")

            dlg1.image(image_name2)

            dlg1.group_title_end

            try:

                dd1 = dlg1.done()

            except ResponseNotOK:

                break

 

            # Update the local namespace

            for key, value in dd1.items():

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

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

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

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

            rotEllipse = dtor(rotEllipse)

           

            if print_doc == "Yes":

                print __doc__

                break

           

            # Auto export default values to disk if enabled

            if enable_default_import_export == "Enable":

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

           

            """

                Calculate points along ellipse in mem1.main_mtrl() coordinates before rotating

                Equation of an ellipse: (x**2/b**2) + (y**2/h**2) = 1

                Equation of resolution ray: mx-y = 0 where m = tan(A)

                Substituting and solving for 'x': x=(b**2*h**2/(h**2+b*m**2))**0.5

                Solving for 'y': y=(((b**2*h**2)-(h**2*x**2))/b**2)**0.5

            """

           

            # initialize local point list

            localRP_list = []

           

            rayIncrement = (pi/2.0)/resEllipse

           

            for i in range(resEllipse + 1):

                xAxis = axis1Ellipse            # b

                yAxis = axis2Ellipse            # h

                xOff = (xAxis**2*yAxis**2/(yAxis**2+(xAxis*tan(rayIncrement * (i)))**2))**0.5

                yOff = (((xAxis**2*yAxis**2)-(yAxis**2*xOff**2))/xAxis**2)**0.5

                localRP_list.append(Point(0.0, yOff, -xOff))

 

            if quadEllipse == "90-180":

                for pt in localRP_list:

                    pt.z = -pt.z

            elif quadEllipse == "180-270":

                for pt in localRP_list:

                    pt.y = -pt.y

                    pt.z = -pt.z

            elif quadEllipse == "270-360":

                for pt in localRP_list:

                    pt.y = -pt.y

            else: # quadEllipse == "0-90", do nothing

                pass

           

            # rotate points into position

            # initialize a new list to store rotated points

            localRP_rot = []

            for pt in localRP_list:

                localRP_rot.append(PointRotate3D(Point(0.0, 0.0, 0.0), Point(1.0, 0.0, 0.0), pt, rotEllipse))

           

            for pt in localRP_rot:

                pt1 = ptWP + mem1.main_mtrl().translate(pt.x, pt.y, pt.z)

                add_RP(pt1, pt1 + a.N_uv, "RP")

           

            if not yes_or_no("Layout another ellipse quadrant?"):

                break

## End run_script() #################################################

if __name__ == '__main__':

    try:

        run_script()

    finally:

        del run_script