#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from subprocess import run

import numpy as np
import open3d as o3d
import scipy.stats
import solid as sd
from importlib_resources import files
from numpy.linalg import norm
from scipy.spatial.transform import Rotation as R

import lyceanem.geometry.geometryfunctions as GF
import lyceanem.raycasting.rayfunctions as RF

EPSILON=1e-6 # how close to zero do we consider zero?

def NasaAlmond(resolution='quarter'):
    """
    NASA Almond is imported and then converted into the appropriate types for modelling
    This depends on the appropriate stl files in the working folder
    Parameters
    ----------
    resolution : TYPE, optional
        DESCRIPTION. The default is 'quarter', right now limited to half,quarter,tenth

    Returns
    -------
    NasaAlmond : o3d triangle mesh
        the physical structure of the nasa almond
    NasaAlmond_points : o3d points
        the mesh points for scattering

    """
    if resolution=='half':
        stream = files('lyceanem.geometry.data').joinpath('NasaAlmondHalfWavelengthv2.stl')
        NasaAlmond=o3d.io.read_triangle_mesh(str(stream))
    elif resolution=='quarter':
        stream = files('lyceanem.geometry.data').joinpath('NasaAlmondQuarterWavelengthv2.stl')
        NasaAlmond=o3d.io.read_triangle_mesh(str(stream))
    elif resolution=='tenth':
        stream = files('lyceanem.geometry.data').joinpath('NasaAlmondTenthWavelengthv2.stl')
        NasaAlmond=o3d.io.read_triangle_mesh(str(stream))

    NasaAlmond.compute_vertex_normals()
    #points=np.asarray(NasaAlmond.vertices)
    #normals=np.asarray(NasaAlmond.vertex_normals)
    _,scatter_cloud=GF.tri_centroids(NasaAlmond)
    return NasaAlmond,scatter_cloud

def source_cloud_from_shape(o3dshape,
                            ideal_point_sep,
                            maxdeviation=0.01):
    """sample surface or mesh and return surface algined points on that mesh

    Parameters
    ----------
    o3dshape : (open3d trianglemesh)
        the surface or shape of interest in open3d trianglemesh format
    ideal_point_sep : (float)
        the desired spacing between each point and it's nearest neighbours, for apertures, this is usually half a wavelength at the highest frequency of interest
    maxdeviation : (float)
        the maximum allowable deviation between the ideal point seperation and the points, as a fraction

    Returns
    ------
    source_cloud : (open3d point cloud)
        the sampled points on the surface, with normal vectors aligned with the surface normal vectors
    areas : (array of float32)
        the area of each triangle in the trianglemesh in world units, as long as the surface is specified in metres, this will be sqm, alinged with the triangle index in the surface
    """
    source_cloud=o3d.geometry.PointCloud()
    o3dshape.compute_triangle_normals()
    vertex_points=np.asarray(o3dshape.vertices)
    norms=np.asarray(o3dshape.triangle_normals)
    area_triangles=np.asarray(o3dshape.triangles)
    #total_area=o3dshape.get_surface_area()
    areas=np.zeros((area_triangles.shape[0]),dtype=np.float32)
    centroids=np.zeros((area_triangles.shape[0],3),dtype=np.float32)
    for tri_index in range(area_triangles.shape[0]):
        areas[tri_index]=np.linalg.norm(np.cross(vertex_points[area_triangles[tri_index,0],:]-vertex_points[area_triangles[tri_index,1],0],vertex_points[area_triangles[tri_index,0],:]-vertex_points[area_triangles[tri_index,2],0]))/2.0
        centroids[tri_index,:]=np.asarray([(vertex_points[area_triangles[tri_index,0],0]+vertex_points[area_triangles[tri_index,1],0]+vertex_points[area_triangles[tri_index,2],0])/3,
                                          (vertex_points[area_triangles[tri_index,0],1]+vertex_points[area_triangles[tri_index,1],1]+vertex_points[area_triangles[tri_index,2],1])/3,
                                          (vertex_points[area_triangles[tri_index,0],2]+vertex_points[area_triangles[tri_index,1],2]+vertex_points[area_triangles[tri_index,2],2])/3])

    #source_cloud.points=o3d.utility.Vector3dVector(centroids+np.array(o3dshape.triangle_normals)*offset)
    #source_cloud.normals=o3dshape.triangle_normals
    #print(np.sum(areas))
    area_per_point=(ideal_point_sep**2)*0.5
    num_points=np.ceil(np.sum(areas)/area_per_point).astype(int)
    source_cloud=o3dshape.sample_points_poisson_disk(num_points)
    error=(ideal_point_sep-np.mean(source_cloud.compute_nearest_neighbor_distance()))/ideal_point_sep
    loopcount=0
    maxloops=20
    errorlog=np.full((maxloops+1,2),np.nan)
    while np.abs(error)>maxdeviation:
        #point sampling is to high, put in control logic for allowable variation in terms of the maxdeviation (fraction of ideal_point_sep)
        print(error)
        loopcount+=1
        errorlog[loopcount,0]=error
        errorlog[loopcount,1]=np.ceil(num_points).astype(int)
        if loopcount>=maxloops:
            print('ran out counter, aborting')
            break
        if error<0:
            #points are too far apart, add more points
            num_points*=(1+np.abs(error))
            source_cloud = o3dshape.sample_points_poisson_disk(np.ceil(num_points).astype(int))
            error = (ideal_point_sep - np.mean(source_cloud.compute_nearest_neighbor_distance())) / ideal_point_sep
        elif error>0:
            #points are to close together, take away points
            num_points *= (1 - np.abs(error))
            source_cloud = o3dshape.sample_points_poisson_disk(np.ceil(num_points).astype(int))
            error = (ideal_point_sep - np.mean(source_cloud.compute_nearest_neighbor_distance())) / ideal_point_sep

    errorlog[loopcount+1,0]=error
    errorlog[loopcount+1,1]=np.ceil(num_points).astype(int)
    
    #ensure that if the while loop cycled past the minimum it still finds it
    optimum_num=errorlog[np.where(np.abs(errorlog[:,0])==np.nanmin(np.abs(errorlog[:,0])))[0][0],1]
    source_cloud = o3dshape.sample_points_poisson_disk(optimum_num.astype(int))
    #breakpoint()
    source_cloud.estimate_normals()
    return source_cloud,areas

def parabola(radius,focal_length,thickness,mesh_length,mesh='all'):
    """
    function to generate parabola of set focal length and radius using solids for more consistent meshing

    Parameters
    ----------
    focal_length : TYPE
        DESCRIPTION.
    radius : TYPE
        DESCRIPTION.
    thickness : TYPE
        DESCRIPTION.
    mesh_length : TYPE
        DESCRIPTION.
    mesh : TYPE, optional
        DESCRIPTION. The default is 'top'.

    Returns
    -------
    parabola_mesh : TYPE
        DESCRIPTION.
    parabola_scattering_points : TYPE
        DESCRIPTION.

    """
    #stream = pkg_resources.resource_stream(__name__, 'parabolas.scad')
    stream=files('lyceanem.geometry').joinpath('parabolas.scad')
    parabolas = sd.import_scad(str(stream))
    height = ((1/(4*focal_length))*radius**2)
    height_external = ((1 / (4 * focal_length)) * (radius+thickness) ** 2)
    #keep with a focal point with zero radius.
    focal_radius=0
    #keep internal for now, gemetry will always be centred with focus a (0,0)
    geometry_centred=0
    #better to be overmeshed than under, so based on circumference
    detail_level=np.ceil((2*scipy.constants.pi*radius)/mesh_length).astype(int)
    parabola_desired=parabolas.openscad_paraboloid(y=height,f=focal_length,rfa=focal_radius,fc=geometry_centred,detail=detail_level)
    parabola_external=parabolas.openscad_paraboloid(y=height_external, f=focal_length, rfa=focal_radius, fc=geometry_centred,
                                                     detail=detail_level)

    final_parabola=sd.difference()(
        sd.translate([0,0,-thickness])(parabola_external),
        sd.union()(
        parabola_desired,
        sd.translate([0,0,height])(sd.cylinder(r=radius,h=height_external,segments=detail_level))
        )
    )

    sd.scad_render_to_file(final_parabola, 'temp.scad')
    # run openscad and export to stl
    run(["openscad-nightly", "-o", "temp.stl", "temp.scad", "--export-format=binstl"])

    parabola_mesh = o3d.io.read_triangle_mesh("temp.stl")
    parabola_mesh.compute_vertex_normals()
    parabola_mesh.compute_triangle_normals()
    _,parabola_scatter_cloud = GF.tri_centroids(parabola_mesh)
    return parabola_mesh,parabola_scatter_cloud

def meshed_pipe(eradius1,eradius2,iradius1,iradius2,height,mesh_length,mesh='centres'):
    """
    creates a cylinder
    Parameters
    ----------
    eradius1 : float
        external bottom radius
    eradius2 : float
        external top radius
    height : float
        DESCRIPTION.
    mesh_length : float
        the maximum length of a facet, and maximum distance between mesh points in fully meshed mode
    mesh : string, optional
        DESCRIPTION. The default is 'centres'. Indicating only the centre of each face should be meshed. If `full' is chosen instead then a fully gridded surface will be generated.'

    Returns
    -------
    shape : open3d triangle mesh of the solid

    mesh : open3d point cloud of the mesh of points with normal vectors

    """

    segment_nums=np.max([10,np.ceil((2*np.pi*np.max([iradius1,iradius2]))/mesh_length).astype('int')])
    centre=sd.difference()(sd.cylinder(r1=eradius1,r2=eradius2,h=height,segments=segment_nums),
                           sd.cylinder(r1=iradius1,r2=iradius2,h=height*1.1,segments=segment_nums))
    if mesh=='centres':
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        mid_radius=(np.min([iradius1,iradius2])+np.abs(iradius1-iradius2)/2)
        saggita=mid_radius*(1-np.cos((2*np.pi/segment_nums)*0.5))
        tilt_angle=np.arctan2(iradius1-iradius2,height)
        test_points=np.asarray([(mid_radius-saggita)*np.cos(angles),(mid_radius-saggita)*np.sin(angles),(height*0.5)*np.ones((segment_nums))]).transpose()
        test_normals=np.asarray([np.cos(angles),np.sin(angles),np.zeros((segment_nums))]).transpose()
        topbottom=np.asarray([[0,0],[0,0],[0,height]]).transpose()
        tb_normals=np.asarray([[0,0],[0,0],[-1,1]]).transpose()
        test_faces=np.append(test_points,topbottom,axis=0)
        face_normals=np.append(test_normals,-1*tb_normals,axis=0)
    elif mesh=='tops':
        #create a mesh for all sides with maximum spacing of mesh_length
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(iradius1-iradius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(iradius1-iradius2,height)
        adjust_radius=np.linspace(iradius1,iradius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(iradius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,iradius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,-1*r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,-1*np.asarray([0,0,1]).reshape(1,3),axis=0)
    elif mesh=='faces':
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(iradius1-iradius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(iradius1-iradius2,height)
        adjust_radius=np.linspace(iradius1,iradius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)

        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

    else:
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(iradius1-iradius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(iradius1-iradius2,height)
        adjust_radius=np.linspace(iradius1,iradius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(iradius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,iradius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)

        #minimum of 1 bottom point
        bottom_segments=np.max([2,np.ceil(iradius1/mesh_length).astype('int')])
        bottom_radials=np.linspace(0,iradius1,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*bottom_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=bottom_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(bottom_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,np.zeros((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),-1*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,-1*col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,-1*np.asarray([0,0,1]).reshape(1,3),axis=0)
        test_faces=np.append(test_faces,np.asarray([0,0,0]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,-1*np.asarray([0,0,-1]).reshape(1,3),axis=0)



    # generate valid openscad code and store it in file
    sd.scad_render_to_file(centre, 'temp.scad')
    # run openscad and export to stl
    run(["openscad-nightly", "-o", "temp.stl", "temp.scad","--export-format=binstl"])

    structure=o3d.io.read_triangle_mesh("temp.stl")
    structure.compute_vertex_normals()
    scatter_cloud=RF.points2pointcloud(np.copy(test_faces))
    scatter_cloud.normals=o3d.utility.Vector3dVector(np.copy(face_normals))

    return structure, scatter_cloud

def meshed_cylinder(radius1,radius2,height,mesh_length,mesh='centres'):
    """
    creates a cylinder
    Parameters
    ----------
    radius1 : float
        bottom radius
    radius2 : float
        top radius
    height : float
        DESCRIPTION.
    mesh_length : float
        the maximum length of a facet, and maximum distance between mesh points in fully meshed mode
    mesh : string, optional
        DESCRIPTION. The default is 'centres'. Indicating only the centre of each face should be meshed. If `full' is chosen instead then a fully gridded surface will be generated.'

    Returns
    -------
    shape : open3d triangle mesh of the solid

    mesh : open3d point cloud of the mesh of points with normal vectors

    """

    segment_nums=np.max([3,np.ceil((2*np.pi*np.max([radius1,radius2]))/mesh_length).astype('int')])
    centre=sd.cylinder(r1=radius1,r2=radius2,h=height,segments=segment_nums)
    if mesh=='centres':
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        mid_radius=(np.min([radius1,radius2])+np.abs(radius1-radius2)/2)
        saggita=mid_radius*(1-np.cos((2*np.pi/segment_nums)*0.5))
        tilt_angle=np.arctan2(radius1-radius2,height)
        test_points=np.asarray([(mid_radius-saggita)*np.cos(angles),(mid_radius-saggita)*np.sin(angles),(height*0.5)*np.ones((segment_nums))]).transpose()
        test_normals=np.asarray([np.cos(angles),np.sin(angles),np.zeros((segment_nums))]).transpose()
        topbottom=np.asarray([[0,0],[0,0],[0,height]]).transpose()
        tb_normals=np.asarray([[0,0],[0,0],[-1,1]]).transpose()
        test_faces=np.append(test_points,topbottom,axis=0)
        face_normals=np.append(test_normals,tb_normals,axis=0)
    elif mesh=='tops':
        #create a mesh for all sides with maximum spacing of mesh_length
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(radius1-radius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(radius1-radius2,height)
        adjust_radius=np.linspace(radius1,radius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(radius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,radius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,1]).reshape(1,3),axis=0)
    elif mesh=='sides':
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(radius1-radius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(radius1-radius2,height)
        adjust_radius=np.linspace(radius1,radius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

    else:
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(radius1-radius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(radius1-radius2,height)
        adjust_radius=np.linspace(radius1,radius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(radius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,radius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 bottom point
        bottom_segments=np.max([2,np.ceil(radius1/mesh_length).astype('int')])
        bottom_radials=np.linspace(0,radius1,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*bottom_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=bottom_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(bottom_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,np.zeros((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),-1*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,1]).reshape(1,3),axis=0)
        test_faces=np.append(test_faces,np.asarray([0,0,0]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,-1]).reshape(1,3),axis=0)



    # generate valid openscad code and store it in file
    sd.scad_render_to_file(centre, 'temp.scad')
    # run openscad and export to stl
    run(["openscad-nightly", "-o", "temp.stl", "temp.scad","--export-format=binstl"])

    structure=o3d.io.read_triangle_mesh("temp.stl")
    structure.compute_vertex_normals()
    scatter_cloud=RF.points2pointcloud(np.copy(test_faces))
    scatter_cloud.normals=o3d.utility.Vector3dVector(np.copy(face_normals))

    return structure, scatter_cloud

def meshed_trapazoid(radius1,radius2,height,mesh_length,mesh='centres'):
    """
    creates a trapazoid
    Parameters
    ----------
    radius1 : float
        bottom radius
    radius2 : float
        top radius
    height : float
        DESCRIPTION.
    mesh_length : float
        the maximum length of a facet, and maximum distance between mesh points in fully meshed mode
    mesh : string, optional
        DESCRIPTION. The default is 'centres'. Indicating only the centre of each face should be meshed. If `full' is chosen instead then a fully gridded surface will be generated.'

    Returns
    -------
    shape : open3d triangle mesh of the solid

    mesh : open3d point cloud of the mesh of points with normal vectors

    """

    #segment_nums=np.max([10,np.ceil((2*np.pi*radius)/mesh_length).astype('int')])
    segment_nums=4
    centre=sd.cylinder(r1=radius1,r2=radius2,h=height,segments=segment_nums)
    if mesh=='centres':
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        mid_radius=(np.min([radius1,radius2])+np.abs(radius1-radius2)/2)
        saggita=mid_radius*(1-np.cos((2*np.pi/segment_nums)*0.5))
        tilt_angle=np.arctan2(radius1-radius2,height)
        test_points=np.asarray([(mid_radius-saggita)*np.cos(angles),(mid_radius-saggita)*np.sin(angles),(height*0.5)*np.ones((segment_nums))]).transpose()
        test_normals=np.asarray([np.cos(angles),np.sin(angles),np.zeros((segment_nums))]).transpose()
        topbottom=np.asarray([[0,0],[0,0],[0,height]]).transpose()
        tb_normals=np.asarray([[0,0],[0,0],[-1,1]]).transpose()
        test_faces=np.append(test_points,topbottom,axis=0)
        face_normals=np.append(test_normals,tb_normals,axis=0)
    elif mesh=='tops':
        #create a mesh for all sides with maximum spacing of mesh_length
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(radius1-radius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(radius1-radius2,height)
        adjust_radius=np.linspace(radius1,radius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(radius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,radius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,1]).reshape(1,3),axis=0)
    else:
        angles=np.linspace(0,np.pi*2,segment_nums+1)[0:-1]+(np.pi)/(segment_nums)
        face_length=(np.abs(radius1-radius2)**2+height**2)**0.5
        face_segments=np.max([3,np.ceil(face_length/mesh_length).astype('int')])
        face_heights=np.linspace(0,height,face_segments)
        test_points=np.empty((0,3),dtype=np.float32)
        test_normals=np.empty((0,3),dtype=np.float32)
        tilt_angle=np.arctan2(radius1-radius2,height)
        adjust_radius=np.linspace(radius1,radius2,face_segments)
        for row_num in range(face_segments):
            segment_chord=2*adjust_radius[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=adjust_radius[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(adjust_radius[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,face_heights[row_num]*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([np.cos(tilt_angle)*np.ones((col_num-1)),np.zeros((col_num-1)),np.sin(tilt_angle)*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 top point
        top_segments=np.max([2,np.ceil(radius2/mesh_length).astype('int')])
        top_radials=np.linspace(0,radius2,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*top_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=top_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(top_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,height*np.ones((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)

        #minimum of 1 bottom point
        bottom_segments=np.max([2,np.ceil(radius1/mesh_length).astype('int')])
        bottom_radials=np.linspace(0,radius1,top_segments)[1:-1]
        for row_num in range(top_segments-2):
            segment_chord=2*bottom_radials[row_num]*np.sin((2*np.pi/segment_nums)*0.5)
            saggita=bottom_radials[row_num]*(1-np.cos((2*np.pi/segment_nums)*0.5))
            col_num=np.max([2,np.ceil(segment_chord/mesh_length).astype('int')])
            col_coords=np.asarray([(bottom_radials[row_num]-saggita)*np.ones((col_num-1)),np.linspace(0,segment_chord,col_num)[0:-1]-segment_chord*0.5,np.zeros((col_num-1))]).transpose()
            col_normals=np.asarray([0*np.ones((col_num-1)),np.zeros((col_num-1)),-1*np.ones((col_num-1))]).transpose()
            test_points=np.append(test_points,col_coords,axis=0)
            test_normals=np.append(test_normals,col_normals,axis=0)


        test_faces=np.empty((0,3),dtype=np.float32)
        face_normals=np.empty((0,3),dtype=np.float32)
        for faces in range(segment_nums):
            #quaternion rotation around z axis
            r = R.from_quat([0, 0, np.sin(angles[faces]/2), np.cos(angles[faces]/2)])
            test_faces=np.append(test_faces,r.apply(test_points),axis=0)
            face_normals=np.append(face_normals,r.apply(test_normals),axis=0)

        #add top centre
        test_faces=np.append(test_faces,np.asarray([0,0,height]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,1]).reshape(1,3),axis=0)
        test_faces=np.append(test_faces,np.asarray([0,0,0]).reshape(1,3),axis=0)
        face_normals=np.append(face_normals,np.asarray([0,0,-1]).reshape(1,3),axis=0)



    # generate valid openscad code and store it in file
    sd.scad_render_to_file(centre, 'temp.scad')
    # run openscad and export to stl
    run(["openscad-nightly", "-o", "temp.stl", "temp.scad","--export-format=binstl"])

    structure=o3d.io.read_triangle_mesh("temp.stl")
    structure.compute_vertex_normals()
    scatter_cloud=RF.points2pointcloud(np.copy(test_faces))
    scatter_cloud.normals=o3d.utility.Vector3dVector(np.copy(face_normals))

    return structure, scatter_cloud

def rectReflector(majorsize,minorsize,thickness):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,0)
    """

    reflector1=o3d.geometry.TriangleMesh.create_box(majorsize,
                                                      minorsize,
                                                      thickness)
    translate_dist=np.array([-majorsize/2.0,-minorsize/2.0,-(thickness+EPSILON)])
    #fine_mesh=reflector1.subdivide_midpoint(3)
    fine_mesh=reflector1
    fine_mesh.compute_vertex_normals()
    fine_mesh.paint_uniform_color([0.79,0.50,0.24])
    fine_mesh.translate(translate_dist,relative=True)

    return fine_mesh

def shapeTrapezoid(x_size,y_size,length,flare_angle):
    """
    create a trapazoid to represent a simple horn of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,0)
    """
    #create trapazoid vertices
    mesh_vertices=np.zeros((8,3),dtype=np.float32)
    mesh_vertices[0,:]=[-x_size/2.,-y_size/2,-1e-6]
    mesh_vertices[1,:]=[x_size/2.,-y_size/2,-1e-6]
    mesh_vertices[2,:]=[x_size/2.,y_size/2,-1e-6]
    mesh_vertices[3,:]=[-x_size/2.,y_size/2,-1e-6]
    mesh_vertices[4,:]=mesh_vertices[0,:]-np.array([mesh_vertices[0,0]*np.sin(flare_angle),mesh_vertices[0,1]*np.sin(flare_angle),length])
    mesh_vertices[5,:]=mesh_vertices[1,:]-np.array([mesh_vertices[1,0]*np.sin(flare_angle),mesh_vertices[1,1]*np.sin(flare_angle),length])
    mesh_vertices[6,:]=mesh_vertices[2,:]-np.array([mesh_vertices[2,0]*np.sin(flare_angle),mesh_vertices[2,1]*np.sin(flare_angle),length])
    mesh_vertices[7,:]=mesh_vertices[3,:]-np.array([mesh_vertices[3,0]*np.sin(flare_angle),mesh_vertices[3,1]*np.sin(flare_angle),length])
    triangle_list=np.zeros((12,3),dtype=np.int32)
    triangle_list[0,:]=[0,1,3]
    triangle_list[1,:]=[2,3,1]
    triangle_list[2,:]=[0,3,4]
    triangle_list[3,:]=[3,7,4]
    triangle_list[4,:]=[3,2,7]
    triangle_list[5,:]=[2,6,7]
    triangle_list[6,:]=[0,4,1]
    triangle_list[7,:]=[5,1,4]
    triangle_list[8,:]=[2,1,6]
    triangle_list[9,:]=[5,6,1]
    triangle_list[10,:]=[4,7,5]
    triangle_list[11,:]=[6,5,7]
    mesh=o3d.geometry.TriangleMesh()
    mesh.vertices=o3d.utility.Vector3dVector(mesh_vertices)
    mesh.triangles=o3d.utility.Vector3iVector(triangle_list)
    mesh.compute_triangle_normals()
    mesh.paint_uniform_color(np.array([0,0.259,0.145]))

    return mesh

def meshedReflector(majorsize,minorsize,thickness,grid_resolution,sides='all'):
    #combined rectReflector and numpy mesh creation to add arbitarily spaced points to a reflector
    reflector=rectReflector(majorsize,minorsize,thickness)
    mesh_points=gridedReflectorPoints(majorsize,minorsize,thickness,grid_resolution,sides)
    return reflector,mesh_points

def OTAEllipsoid(reflector_index,major_axis_size,minor_axis_size,max_grid,reflector_shape='rectangle'):
    #standard defintion of the OTA Prototpe
    #antenna 1 should be at (-0.98107,0,0)
    #antenna 2 should be at (0.98107,0,0)
    #major axis of 2.05m, minor axis of 1.8m

    #
    gridded_sides='front'
    semi_major=2.05
    semi_minor=1.8
    freq=24e9
    wavelength=3e8/freq
    local_coords=np.array([[-1.9998,0.2781,0],[-1.8041,0.8071,0],[-1.4317,1.2571,0],[-0.9192,1.5841,0],[-0.3167,1.7560,0]])
    full_coords=np.append(local_coords,np.flipud(local_coords),axis=0)
    full_coords[5:,0]=full_coords[5:,0]*-1
    #
    origins=np.zeros((10,3),dtype=np.float32)
    directions=np.zeros((10,3),dtype=np.float32)
    norm_length=np.zeros((10,1),dtype=np.float32)
    directions,norm_length=RF.calc_dv_norm(full_coords,origins,directions,norm_length)
    angle_values=np.arctan2(directions[:,1],directions[:,0])
    reflector_pointers=np.arctan((semi_major**2)/(semi_minor**2)*np.tan(angle_values))-np.pi/2.0
    if (reflector_index==0 or reflector_index>=10):
        if reflector_shape=='rectangle':
            s1_plate,s1_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            s1_plate,s1_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        s1_points=GF.open3drotate(s1_points,
                                  o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])))
        s1_points = GF.open3drotate(s1_points,
                                    o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[0]]))

        s1_points.translate(full_coords[0,:].transpose(),relative=True)
        s1_plate=GF.open3drotate(s1_plate,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])))
        s1_plate=GF.open3drotate(s1_plate,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[0]]))
        s1_plate.translate(full_coords[0,:].transpose(),relative=True)

    if (reflector_index==1 or reflector_index>=10):
        if reflector_shape=='rectangle':
            u1_plate,u1_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            u1_plate,u1_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        u1_plate=GF.open3drotate(u1_plate,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])))
        u1_plate=GF.open3drotate(u1_plate,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[1]]))
        u1_plate.translate(full_coords[1,:].transpose(),relative=True)
        u1_points=GF.open3drotate(u1_points,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])))
        u1_points=GF.open3drotate(u1_points,o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[1]]))
        u1_points.translate(full_coords[1,:].transpose(),relative=True)

    if (reflector_index==2 or reflector_index>=10):
        if reflector_shape=='rectangle':
            v1_plate,v1_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            v1_plate,v1_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        v1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        v1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[2]]),center=False)
        v1_plate.translate(full_coords[2,:].transpose(),relative=True)
        v1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        v1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[2]]),center=False)
        v1_points.translate(full_coords[2,:].transpose(),relative=True)

    if (reflector_index==3 or reflector_index>=10):
        if reflector_shape=='rectangle':
            w1_plate,w1_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            w1_plate,w1_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        w1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        w1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[3]]),center=False)
        w1_plate.translate(full_coords[3,:].transpose(),relative=True)
        w1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        w1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[3]]),center=False)
        w1_points.translate(full_coords[3,:].transpose(),relative=True)

    if (reflector_index==4 or reflector_index>=10):
        if reflector_shape=='rectangle':
            z1_plate,z1_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            z1_plate,z1_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        z1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        z1_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[4]]),center=False)
        z1_plate.translate(full_coords[4,:].transpose(),relative=True)
        z1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        z1_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[4]]),center=False)
        z1_points.translate(full_coords[4,:].transpose(),relative=True)

    if (reflector_index==5 or reflector_index>=10):
        if reflector_shape=='rectangle':
            z2_plate,z2_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            z2_plate,z2_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        z2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        z2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[5]+np.pi]),center=False)
        z2_plate.translate(full_coords[5,:].transpose(),relative=True)
        z2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        z2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[5]+np.pi]),center=False)
        z2_points.translate(full_coords[5,:].transpose(),relative=True)

    if (reflector_index==6 or reflector_index>=10):
        if reflector_shape=='rectangle':
            w2_plate,w2_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            w2_plate,w2_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        w2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        w2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[6]+np.pi]),center=False)
        w2_plate.translate(full_coords[6,:].transpose(),relative=True)
        w2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        w2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[6]+np.pi]),center=False)
        w2_points.translate(full_coords[6,:].transpose(),relative=True)

    if (reflector_index==7 or reflector_index>=10):
        if reflector_shape=='rectangle':
            v2_plate,v2_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            v2_plate,v2_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        v2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        v2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[7]+np.pi]),center=False)
        v2_plate.translate(full_coords[7,:].transpose(),relative=True)
        v2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        v2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[7]+np.pi]),center=False)
        v2_points.translate(full_coords[7,:].transpose(),relative=True)

    if (reflector_index==8 or reflector_index>=10):
        if reflector_shape=='rectangle':
            u2_plate,u2_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            u2_plate,u2_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        u2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        u2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[8]+np.pi]),center=False)
        u2_plate.translate(full_coords[8,:].transpose(),relative=True)
        u2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        u2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[8]+np.pi]),center=False)
        u2_points.translate(full_coords[8,:].transpose(),relative=True)

    if (reflector_index==9 or reflector_index>=10):
        if reflector_shape=='rectangle':
            s2_plate,s2_points=meshedReflector(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)
        elif reflector_shape=='circle':
            s2_plate,s2_points=meshedCircle(major_axis_size,minor_axis_size,6e-3,max_grid,sides=gridded_sides)

        s2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        s2_plate.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[9]+np.pi]),center=False)
        s2_plate.translate(full_coords[9,:].transpose(),relative=True)
        s2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz(np.asarray([np.arcsin(-1.0),0.0,0.0])),center=False)
        s2_points.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_xyz([0.0,0.0,reflector_pointers[9]+np.pi]),center=False)
        s2_points.translate(full_coords[9,:].transpose(),relative=True)
    #mesh_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=1, origin=[0, 0, 0])
    #o3d.visualization.draw_geometries([s1_plate,u1_plate,v1_plate,w1_plate,z1_plate,s1_points,u1_points,v1_points,w1_points,z1_points,mesh_frame])
    if (reflector_index==0):
        #total_scatter_points=points2pointcloud(np.asarray(s1_points.points))
        total_scatter_points=s1_points
        environment=RF.convertTriangles(s1_plate)
        all_plates=s1_plate

    if (reflector_index==1):
        #total_scatter_points=points2pointcloud(np.asarray(u1_points.points))
        total_scatter_points=u1_points
        environment=RF.convertTriangles(u1_plate)
        all_plates=u1_plate

    if (reflector_index==2):
        #total_scatter_points=points2pointcloud(np.asarray(v1_points.points))
        total_scatter_points=v1_points
        environment=RF.convertTriangles(v1_plate)
        all_plates=v1_plate

    if (reflector_index==3):
        #total_scatter_points=points2pointcloud(np.asarray(w1_points.points))
        total_scatter_points=w1_points
        environment=RF.convertTriangles(w1_plate)
        all_plates=w1_plate

    if (reflector_index==4):
        #total_scatter_points=points2pointcloud(np.asarray(z1_points.points))
        total_scatter_points=z1_points
        environment=RF.convertTriangles(z1_plate)
        all_plates=z1_plate

    if (reflector_index==5):
        #total_scatter_points=points2pointcloud(np.asarray(z2_points.points))
        total_scatter_points=z2_points
        environment=RF.convertTriangles(z2_plate)
        all_plates=z2_plate

    if (reflector_index==6):
        #total_scatter_points=points2pointcloud(np.asarray(w2_points.points))
        total_scatter_points=w2_points
        environment=RF.convertTriangles(w2_plate)
        all_plates=w2_plate

    if (reflector_index==7):
        #total_scatter_points=points2pointcloud(np.asarray(v2_points.points))
        total_scatter_points=v2_points
        environment=RF.convertTriangles(v2_plate)
        all_plates=v2_plate

    if (reflector_index==8):
        #total_scatter_points=points2pointcloud(np.asarray(u2_points.points))
        total_scatter_points=u2_points
        environment=RF.convertTriangles(u2_plate)
        all_plates=u2_plate

    if (reflector_index==9):
        #total_scatter_points=points2pointcloud(np.asarray(s2_points.points))
        total_scatter_points=s2_points
        environment=RF.convertTriangles(s2_plate)
        all_plates=s2_plate

    if (reflector_index>=10):
        total_scatter_temp=RF.points2pointcloud(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.asarray(s1_points.points),np.asarray(u1_points.points),axis=0),np.asarray(v1_points.points),axis=0),np.asarray(w1_points.points),axis=0),np.asarray(z1_points.points),axis=0),np.asarray(z2_points.points),axis=0),np.asarray(w2_points.points),axis=0),np.asarray(v2_points.points),axis=0),np.asarray(u2_points.points),axis=0),np.asarray(s2_points.points),axis=0))
        total_scatter_normals=RF.points2pointcloud(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.asarray(s1_points.normals),np.asarray(u1_points.normals),axis=0),np.asarray(v1_points.normals),axis=0),np.asarray(w1_points.normals),axis=0),np.asarray(z1_points.normals),axis=0),np.asarray(z2_points.normals),axis=0),np.asarray(w2_points.normals),axis=0),np.asarray(v2_points.normals),axis=0),np.asarray(u2_points.normals),axis=0),np.asarray(s2_points.normals),axis=0))
        total_scatter_points=o3d.geometry.PointCloud()
        total_scatter_points.points=o3d.utility.Vector3dVector(total_scatter_temp)
        total_scatter_points.normals=o3d.utility.Vector3dVector(total_scatter_normals)
        environment=np.append(np.append(np.append(np.append(np.append(np.append(np.append(np.append(RF.convertTriangles(s1_plate),RF.convertTriangles(u1_plate)),RF.convertTriangles(v1_plate)),RF.convertTriangles(w1_plate)),RF.convertTriangles(z1_plate)),RF.convertTriangles(z2_plate)),RF.convertTriangles(w2_plate)),RF.convertTriangles(v2_plate)),RF.convertTriangles(u2_plate))
        all_plates=[s1_plate,u1_plate,v1_plate,w1_plate,z1_plate,s2_plate,u2_plate,v2_plate,w2_plate,z2_plate]

    return environment,total_scatter_points,all_plates

def circleReflector(majorsize,minorsize,thickness,grid_resolution):
    #create a circle
    circ_num=np.ceil((np.pi*majorsize)/grid_resolution).astype(int)
    mesh=o3d.geometry.TriangleMesh.create_cylinder(radius=majorsize/2,height=thickness,resolution=circ_num,split=1)
    translate_vector=np.asarray([0,0,-(thickness/2+1e-6)])
    mesh.translate(translate_vector,relative=True)
    mesh.compute_vertex_normals()
    mesh.paint_uniform_color([0.79,0.50,0.24])
    return mesh

def meshedCircle(majorsize,minorsize,thickness,grid_resolution,sides='all'):
    #define a circle, and grid the front and back
    reflector=circleReflector(majorsize,minorsize,thickness,grid_resolution)
    mesh_points=gridedCirclePoints(majorsize,minorsize,thickness,grid_resolution,sides='front')
    return reflector,mesh_points

def meshedHorn(majorsize,minorsize,length,edge_width,flare_angle,grid_resolution,sides='front'):
    #simplified horn with solid backing to model forward propagation only
    structure=shapeTrapezoid(majorsize+(edge_width*2),minorsize+(edge_width*2),length,flare_angle)
    mesh_points=gridedReflectorPoints(majorsize,minorsize,1e-6,grid_resolution,sides)
    return structure,mesh_points

def coneReflector(radius,height):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,1)
    """
    resolution=200000
    split=1
    reflector1=o3d.geometry.TriangleMesh.create_cone(radius,
                                                      height,
                                                      resolution,
                                                      split)
    translate_dist=np.array([0,0,3])
    reflector1.rotate(o3d.geometry.TriangleMesh.get_rotation_matrix_from_axis_angle(np.array([0.0,np.radians(180),0.0])),center=True)
    reflector1.compute_vertex_normals()
    reflector1.paint_uniform_color([0.79,0.50,0.24])
    reflector1.translate(translate_dist,relative=True)

    return reflector1

def ringarray(ring_rad,elements,angle_offset,backing_rad,thickness,grid_resolution=20):
    #define a single ring array, backed by a circular reflector
    reflector=circleReflector(backing_rad,backing_rad,thickness,grid_resolution)
    #define ring array, with element 0 on x axis
    element_angles=np.linspace(angle_offset,2*np.pi-angle_offset,elements)
    element_locations=np.zeros((elements,3),dtype=np.float32)
    element_normals=np.zeros((elements,3),dtype=np.float32)
    element_normals[:,2]=1.0
    element_locations[:,0]=ring_rad*np.cos(element_angles)
    element_locations[:,1]=ring_rad*np.sin(element_angles)
    mesh_points=o3d.geometry.PointCloud()
    mesh_points.points=o3d.utility.Vector3dVector(np.copy(element_locations))
    mesh_points.normals=o3d.utility.Vector3dVector(np.copy(element_normals))
    return reflector,mesh_points


def gridedCirclePoints(majorsize,minorsize,thickness,grid_resolution,sides='front'):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,1)
    """
    if sides=='all':
        r_space=np.linspace(grid_resolution,(majorsize/2),int(np.max(np.asarray([2,np.ceil((majorsize*0.5)/(grid_resolution))]))))
        c_space=np.ceil((2*np.pi*r_space)/grid_resolution).astype(int)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,0]=0
        source_coords[0,1]=0
        source_coords[0,2]=0
        for r_index in range(r_space.shape[0]):
            source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),np.zeros(c_space[r_index]-1)]).transpose(),axis=0)

        source_normals=np.empty(((source_coords.shape[0],3)),dtype=np.float32)
        source_normals[:]=0.
        source_normals[:,2]=1.
        mesh_vertices=source_coords
        #temp removed sides
        #mesh_vertices=np.append(source_coords,back_coords,axis=0)
        mesh_normals=source_normals
    elif(sides=='front'):
        r_space=np.linspace(grid_resolution,(majorsize/2),int(np.max(np.asarray([2,np.ceil((majorsize*0.5)/(grid_resolution))]))))
        c_space=np.ceil((2*np.pi*r_space)/grid_resolution).astype(int)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,0]=0
        source_coords[0,1]=0
        source_coords[0,2]=0
        for r_index in range(r_space.shape[0]):
            source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),np.zeros(c_space[r_index]-1)]).transpose(),axis=0)

        source_normals=np.empty(((source_coords.shape[0],3)),dtype=np.float32)
        source_normals[:]=0.
        source_normals[:,2]=1.
        mesh_vertices=source_coords
        #temp removed sides
        #mesh_vertices=np.append(source_coords,back_coords,axis=0)
        mesh_normals=source_normals


    mesh_points=o3d.geometry.PointCloud()
    mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
    mesh_points.normals=o3d.utility.Vector3dVector(np.copy(mesh_normals))
    return mesh_points

def gridedBullsEyePoints(diameter,ring_start,ring_num,ring_wavelength,ring_amplitude,thickness,grid_resolution,sides='all'):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,1)
    """
    if sides=='front':
        sine_start=5e-3
        #ring_num=2
        #sine_wavelength=5e-3
        #sine_amplitude=(0.91/2)*1e-3
        r_space=np.linspace(grid_resolution,(diameter/2),int(np.max(np.asarray([2,np.ceil((diameter*0.5)/(grid_resolution))]))))
        c_space=np.ceil((2*np.pi*r_space)/grid_resolution).astype(int)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,0]=0
        source_coords[0,1]=0
        source_coords[0,2]=0
        for r_index in range(r_space.shape[0]):
            if ring_start <= r_space[r_index] <= (ring_start + ring_num * ring_wavelength):
                source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),ring_amplitude*np.sin((r_space[r_index]-ring_start)*((np.pi*2)/ring_wavelength)+np.pi/2)*np.ones(c_space[r_index]-1)-(ring_amplitude)]).transpose(),axis=0)
            else:
                source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),np.zeros(c_space[r_index]-1)]).transpose(),axis=0)


        #source_normals=np.empty(((source_coords.shape[0],3)),dtype=np.float32)
        #source_normals[:]=0.
        #source_normals[:,2]=1.
        mesh_vertices=source_coords
        #temp removed sides
        #mesh_vertices=np.append(source_coords,back_coords,axis=0)
        #mesh_normals=source_normals


        mesh_points=o3d.geometry.PointCloud()
        mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
        mesh_points.estimate_normals()
    elif sides=='all':
        r_space=np.linspace(grid_resolution,(diameter/2),int(np.max(np.asarray([2,np.ceil((diameter*0.5)/(grid_resolution))]))))
        c_space=np.ceil((2*np.pi*r_space)/grid_resolution).astype(int)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,0]=0
        source_coords[0,1]=0
        source_coords[0,2]=0
        for r_index in range(r_space.shape[0]):
            if ring_start <= r_space[r_index] <= (ring_start + ring_num * ring_wavelength):
                source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),ring_amplitude*np.sin((r_space[r_index]-ring_start)*((np.pi*2)/ring_wavelength)+np.pi/2)*np.ones(c_space[r_index]-1)-(ring_amplitude)]).transpose(),axis=0)
            else:
                source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),np.zeros(c_space[r_index]-1)]).transpose(),axis=0)

            source_coords=np.append(source_coords,np.array([r_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),r_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),np.zeros(c_space[r_index]-1)-thickness]).transpose(),axis=0)

        source_coords=np.append(source_coords,np.array([r_space[-1]*np.cos(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),r_space[-1]*np.sin(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),np.zeros(c_space[-1]-1)-thickness/2]).transpose(),axis=0)
        source_coords=np.append(source_coords,np.array([r_space[-1]*np.cos(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),r_space[-1]*np.sin(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),np.zeros(c_space[-1]-1)-thickness]).transpose(),axis=0)
        source_coords=np.append(source_coords,np.array([0.0,0.0,-thickness]).reshape(1,3),axis=0)
        mesh_vertices=source_coords


        mesh_points=o3d.geometry.PointCloud()
        mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
        mesh_points.estimate_normals()

    return mesh_points

def gridedReflectorPoints(majorsize,minorsize,thickness,grid_resolution,sides='all'):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,1)
    """
    if sides=='all':
        x=np.linspace(-(majorsize/2),(majorsize/2),int(np.max(np.asarray([2,np.ceil(majorsize/(grid_resolution))]))))
        y=np.linspace(-(minorsize/2),(minorsize/2),int(np.max(np.asarray([2,np.ceil(minorsize/(grid_resolution))]))))
        xback=np.linspace(-(majorsize/2),(majorsize/2),int(np.max(np.asarray([2,np.ceil(majorsize/(grid_resolution))]))))
        yback=np.linspace(-(minorsize/2),(minorsize/2),int(np.max(np.asarray([2,np.ceil(minorsize/(grid_resolution))]))))
        majorsidey=np.linspace(-(minorsize/2),(minorsize/2),int(np.max(np.asarray([2,np.ceil(minorsize/grid_resolution)]))))
        majorsidez=np.linspace(-thickness,0,int(np.max(np.asarray([2,np.ceil(thickness/grid_resolution)]))))
        minorsidex=np.linspace(-(majorsize/2),(majorsize/2),int(np.max(np.asarray([2,np.ceil(majorsize/grid_resolution)]))))
        minorsidez=np.linspace(-thickness,0,int(np.max(np.asarray([2,np.ceil(thickness/grid_resolution)]))))
        xx,yy=np.meshgrid(x,y)
        my,mz=np.meshgrid(majorsidey,majorsidez)
        mx=np.zeros((len(np.ravel(my))))
        majorside1=np.empty(((len(np.ravel(mx)),3)),dtype=np.float32)
        majorside2=np.empty(((len(np.ravel(mx)),3)),dtype=np.float32)
        majorside1[:,0]=mx+majorsize/2.0#+EPSILON
        majorside1[:,1]=np.ravel(my)
        majorside1[:,2]=np.ravel(mz)
        majorside2[:,0]=mx-majorsize/2.0#-EPSILON
        majorside2[:,1]=np.ravel(my)
        majorside2[:,2]=np.ravel(mz)
        majorside1normals=np.empty(((len(np.ravel(mx)),3)),dtype=np.float32)
        majorside2normals=np.empty(((len(np.ravel(mx)),3)),dtype=np.float32)
        majorside1normals[:]=0
        majorside2normals[:]=0
        majorside1normals[:,0]=1.
        majorside2normals[:,0]=-1.
        max1,maz1=np.meshgrid(minorsidex,minorsidez)
        may=np.zeros((len(np.ravel(max1))))
        minorside1=np.empty(((len(np.ravel(max1)),3)),dtype=np.float32)
        minorside2=np.empty(((len(np.ravel(max1)),3)),dtype=np.float32)
        minorside1[:,0]=np.ravel(max1)
        minorside1[:,1]=np.ravel(may)+minorsize/2.0#+EPSILON
        minorside1[:,2]=np.ravel(maz1)
        minorside2[:,0]=np.ravel(max1)
        minorside2[:,1]=np.ravel(may)-minorsize/2.0#-EPSILON
        minorside2[:,2]=np.ravel(maz1)
        minorside1normals=np.empty(((len(np.ravel(max1)),3)),dtype=np.float32)
        minorside2normals=np.empty(((len(np.ravel(max1)),3)),dtype=np.float32)
        minorside1normals[:]=0
        minorside2normals[:]=0
        minorside1normals[:,1]=1.
        minorside2normals[:,1]=-1.
        zz=np.zeros((len(np.ravel(xx)),1))#+0.00025
        source_coords=np.empty(((len(np.ravel(xx)),3)),dtype=np.float32)
        source_normals=np.empty(((len(np.ravel(xx)),3)),dtype=np.float32)
        source_normals[:]=0.
        source_normals[:,2]=1.
        source_coords[:,0]=np.ravel(xx)
        source_coords[:,1]=np.ravel(yy)
        source_coords[:,2]=np.ravel(zz)
        backx,backy=np.meshgrid(xback,yback)
        backz=np.empty((len(np.ravel(backx)),1),dtype=np.float32)
        backz[:]=-thickness
        back_coords=np.empty(((len(np.ravel(backx)),3)),dtype=np.float32)
        back_normals=np.empty(((len(np.ravel(backx)),3)),dtype=np.float32)
        back_coords[:,0]=np.ravel(backx)
        back_coords[:,1]=np.ravel(backy)
        back_coords[:,2]=np.ravel(backz)
        back_normals[:]=0.
        back_normals[:,2]=-1.
        top_bottom=np.append(majorside1,majorside2,axis=0)
        top_bottom_normals=np.append(majorside1normals,majorside2normals,axis=0)
        left_right=np.append(minorside1,minorside2,axis=0)
        left_right_normals=np.append(minorside1normals,minorside2normals,axis=0)
        sides=np.append(top_bottom,left_right,axis=0)
        side_normals=np.append(top_bottom_normals,left_right_normals,axis=0)
        mesh_vertices=np.append(np.append(source_coords,back_coords,axis=0),sides,axis=0)
        #temp removed sides
        #mesh_vertices=np.append(source_coords,back_coords,axis=0)
        mesh_normals=np.append(np.append(source_normals,back_normals,axis=0),side_normals,axis=0)
        #mesh_normals=np.append(source_normals,back_normals,axis=0)
        mesh_points=o3d.geometry.PointCloud()
        mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
        mesh_points.normals=o3d.utility.Vector3dVector(np.copy(mesh_normals))
    elif(sides=='front'):
        x=np.linspace(-(majorsize/2),(majorsize/2),int(np.max(np.asarray([2,np.ceil(majorsize/(grid_resolution))]))))
        y=np.linspace(-(minorsize/2),(minorsize/2),int(np.max(np.asarray([2,np.ceil(minorsize/(grid_resolution))]))))
        xx,yy=np.meshgrid(x,y)
        zz=np.zeros((len(np.ravel(xx)),1))#+0.00025
        source_coords=np.empty(((len(np.ravel(xx)),3)),dtype=np.float32)
        source_normals=np.empty(((len(np.ravel(xx)),3)),dtype=np.float32)
        source_normals[:]=0.
        source_normals[:,2]=1.
        source_coords[:,0]=np.ravel(xx)
        source_coords[:,1]=np.ravel(yy)
        source_coords[:,2]=np.ravel(zz)
        mesh_vertices=source_coords
        #temp removed sides
        #mesh_vertices=np.append(source_coords,back_coords,axis=0)
        mesh_normals=source_normals
    elif(sides=='centres'):
        source_coords=np.zeros((1,3),dtype=np.float32)
        source_normals=np.zeros((1,3),dtype=np.float32)
        source_normals[0,2]=1
        mesh_vertices=source_coords
        mesh_normals=source_normals


    mesh_points=o3d.geometry.PointCloud()
    mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
    mesh_points.normals=o3d.utility.Vector3dVector(np.copy(mesh_normals))
    return mesh_points

def gridedParabola(diameter,focal_length,thickness,grid_resolution,sides='all'):
    """
    create a primative of the right size, assuming always orientated
    with normal aligned with zenith, and major axis with x,
    adjust position so the face is centred on (0,0,1)
    equation of parabola is y=(1/(4*focal_length))*x**2, this will then be rotated in the Az domain.
    Need to ensure that the euclidean distance between each point is grid_resolution or less
    """
    if sides=='all':
        x_space=np.linspace(grid_resolution,(diameter/2),int(np.max(np.asarray([2,np.ceil((diameter*0.5)/(grid_resolution))]))))
        z_space=(1/(4*focal_length))*x_space**2
        c_space=np.ceil((2*np.pi*x_space)/grid_resolution).astype(int)
        normal_gradiant_vector=np.array([np.ones((len(x_space))),np.zeros((len(x_space))),-1/(1/(2*focal_length)*x_space)])
        source=np.array([x_space,np.zeros((len(x_space))),z_space]).transpose()
        target=(normal_gradiant_vector*-x_space).transpose()
        base_directions=np.zeros((x_space.shape[0],3),dtype=np.float32)
        norm_length=np.zeros((x_space.shape[0],1),dtype=np.float32)
        base_directions,norm_length=RF.calc_dv_norm(source,target,base_directions,norm_length)
        base_directions=np.append(base_directions,np.array([[1,0,0]]),axis=0)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,:]=0
        source_normals=np.empty(((1,3)),dtype=np.float32)
        source_normals[0,:]=0
        source_normals[0,2]=1
        for r_index in range(x_space.shape[0]):
            source_coords=np.append(source_coords,np.array([x_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            x_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            z_space[r_index]*np.ones(c_space[r_index]-1)]).transpose(),axis=0)
            source_normals=np.append(source_normals,np.array([base_directions[r_index,0]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                              base_directions[r_index,0]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                              base_directions[r_index,2]*np.ones(c_space[r_index]-1)]).transpose(),axis=0)

        mesh_vertices=np.append(np.append(source_coords-np.array([0,0,np.max(source_coords[:,2])]),source_coords-np.array([0,0,(np.max(source_coords[:,2])+thickness)]),axis=0),np.array([x_space[-1]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            x_space[-1]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            z_space[-1]*np.ones(c_space[-1]-1)]).transpose()-np.array([0,0,(np.max(source_coords[:,2])+thickness/2)]),axis=0)
        mesh_normals=np.append(np.append(source_normals,source_normals*-1,axis=0),np.array([base_directions[-1,0]*np.cos(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),
                                                              base_directions[-1,0]*np.sin(np.linspace(0,(2*np.pi),c_space[-1])[0:-1]),
                                                              np.zeros(c_space[-1]-1)]).transpose(),axis=0)

    elif(sides=='front'):
        x_space=np.linspace(grid_resolution,(diameter/2),int(np.max(np.asarray([2,np.ceil((diameter*0.5)/(grid_resolution))]))))
        z_space=(1/(4*focal_length))*x_space**2
        c_space=np.ceil((2*np.pi*x_space)/grid_resolution).astype(int)
        normal_gradiant_vector=np.array([np.ones((len(x_space))),np.zeros((len(x_space))),-1/(1/(2*focal_length)*x_space)])
        source=np.array([x_space,np.zeros((len(x_space))),z_space]).transpose()
        target=(normal_gradiant_vector*-x_space).transpose()
        base_directions=np.zeros((x_space.shape[0],3),dtype=np.float32)
        norm_length=np.zeros((x_space.shape[0],1),dtype=np.float32)
        base_directions,norm_length=RF.calc_dv_norm(source,target,base_directions,norm_length)
        source_coords=np.empty(((1,3)),dtype=np.float32)
        source_coords[0,:]=0
        source_normals=np.empty(((1,3)),dtype=np.float32)
        source_normals[0,:]=0
        source_normals[0,2]=1
        for r_index in range(x_space.shape[0]):
            source_coords=np.append(source_coords,np.array([x_space[r_index]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            x_space[r_index]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                            z_space[r_index]*np.ones(c_space[r_index]-1)]).transpose(),axis=0)
            source_normals=np.append(source_normals,np.array([base_directions[r_index,0]*np.cos(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                              base_directions[r_index,0]*np.sin(np.linspace(0,(2*np.pi),c_space[r_index])[0:-1]),
                                                              base_directions[r_index,2]*np.ones(c_space[r_index]-1)]).transpose(),axis=0)

        mesh_vertices=source_coords-np.array([0,0,np.max(source_coords[:,2])])
        mesh_normals=source_normals


    mesh_points=o3d.geometry.PointCloud()
    mesh_points.points=o3d.utility.Vector3dVector(np.copy(mesh_vertices))
    mesh_points.normals=o3d.utility.Vector3dVector(np.copy(mesh_normals))
    return mesh_points

def BullsEye(O2,n_rings,innerdia,period,ht,basethick,grid_resolution):
    #create bullseye lens structure on a reflector, this is a temp setup
    rotate_nums=31
    # make 5 rings with a preiod of 5mm between them.
    #period=4.53e-3# period of the rings
    #n_rings=2# number of rings to make
    #ht=(0.91e-3)/2# amplitude  of ring waves
    m_rings=0# ring to start at
    #innerdia=7.12e-3# diameter of inner ring
    #basethick=3e-3# thickness of the base below the bottom of the slots5
    #O2=2e-3
    #generate appropriatly paced sine curve for face of array, then convert to point list with .tolist()
    #convert with grid resolution between each points, from innerdia to (innerdia+period*n_rings+)
    points2d=np.zeros()
    #face_curve=points2d.tolist()

    centre=sd.translate([0,0,-basethick])(sd.cylinder(r=innerdia/2+m_rings*period+period*0.01,h=basethick,segments=rotate_nums))
    for p in range(m_rings,n_rings+1):
        centre+=sd.rotate_extrude(angle=360, segments=rotate_nums)(
            sd.translate([innerdia/2+p*period,0,0])(
                sd.scale([period,ht,1])(
                    sd.polygon(points=[[0.000000,0.000000],[0.025000,-0.012312],[0.050000,-0.048943],[0.075000,-0.108993],[0.100000,-0.190983],[0.125000,-0.292893],[0.150000,-0.412215],[0.175000,-0.546010],[0.200000,-0.690983],[0.225000,-0.843566],[0.250000,-1.000000],[0.275000,-1.156434],[0.300000,-1.309017],[0.325000,-1.453990],[0.350000,-1.587785],[0.375000,-1.707107],[0.400000,-1.809017],[0.425000,-1.891007],[0.450000,-1.951057],[0.475000,-1.987688],[0.500000,-2.000000],[0.525000,-1.987688],[0.550000,-1.951057],[0.575000,-1.891007],[0.600000,-1.809017],[0.625000,-1.707107],[0.650000,-1.587785],[0.675000,-1.453990],[0.700000,-1.309017],[0.725000,-1.156434],[0.750000,-1.000000],[0.775000,-0.843566],[0.800000,-0.690983],[0.825000,-0.546010],[0.850000,-0.412215],[0.875000,-0.292893],[0.900000,-0.190983],[0.925000,-0.108993],[0.950000,-0.048943],[0.975000,-0.012312],[1.000000,0.000000],[1,-basethick/ht],[0,-basethick/ht]])

        )))


    centre+=sd.translate([0,0,-basethick/2])(sd.cylinder(r=innerdia/2+(n_rings+1)*period+O2,h=basethick,center=True,segments=rotate_nums)
                 -sd.cylinder(r=innerdia/2+(n_rings+1)*period-period*0.01,h=2*basethick,center=True,segments=rotate_nums))


    # generate valid openscad code and store it in file
    sd.scad_render_to_file(centre, 'd.scad')

    # run openscad and export to stl
    run(["openscad-nightly", "-o", "d.stl", "d.scad","--export-format=binstl"])

    solid=o3d.io.read_triangle_mesh("d.stl")
    solid.compute_vertex_normals()
    #o3d.visualization.draw_geometries([test_structure])

    test_points=np.asarray(solid.vertices)
    test_normals=np.asarray(solid.vertex_normals)
    image_points=RF.points2pointcloud(np.copy(test_points[test_points[:,2]>=-ht*2,:]))
    image_points.normals=o3d.utility.Vector3dVector(np.copy(test_normals[test_points[:,2]>=-ht*2,:]))
    return solid,image_points

def defineParabola(diameter,focal_length,thickness,grid_resolution):
    #create shape from points
    mesh_points=gridedParabola(diameter,focal_length,thickness,grid_resolution,sides='all')
    distances = mesh_points.compute_nearest_neighbor_distance()
    avg_dist = np.mean(distances)
    radius = 1.5 * avg_dist
    mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
    mesh_points,
    o3d.utility.DoubleVector([radius, radius * 2]))
    #mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
    #       mesh_points,
    #       o3d.utility.DoubleVector([radius, radius * 2]))

    mesh.compute_triangle_normals()
    return mesh

def parabolic_reflector_segment():
    """
    This function is intended to generate a segment parabolic reflector, such as used for some radars
    """
    reflector=o3d.geometry.TriangleMesh()
    mesh_points=o3d.geometry.PointCloud()
    return reflector,mesh_points
