def xulaconnector(): screw_xuout = 2.5 screw_xuin = 1.5 screw_toph = 1 offset = 5 length = offset + THICK_WALL # XULA2 is attached to the top with two screws screw_xula = hscrew(screw_xuout, screw_toph, screw_xuin, length) screws_xula = screw_xula + right(58)(screw_xula) screws_xula += up(48.8)(screws_xula) xula_base = screws_xula # Raspberry connector xula2 rsp_cnctr = cube([58 - 2 * (screw_xuin + THICK_WALL * 0.5), length, 6]) xula_base += translate([screw_xuin + THICK_WALL * 0.5, -length, -3 + 48.8])(hole()(rsp_cnctr)) # add stickit connector top_height = 4 # top height screw in mm top_r = 3.5 # top r screw in mm shaft_r = 2 screw_stick = hscrew(top_r, top_height, shaft_r, length) screws_stick = screw_stick + up(15)(screw_stick) # stickit length 49.6-1.28-2-2 # 15.5+1.5+1.2-1.5 xula_base += translate( [-(49.6 - 1.28 - 2 - 2) - 8, 0, 15.5 + 1.5 + 1.2 - 1.5 - 2.7])(screws_stick) xula_base += translate([-60, -2, -8])(cube([123, 2, 61])) xula_base = up(THICK_WALL + 11)(xula_base) # add down connector base_exit = cube([58 + 2 * screw_xuin + THICK_WALL, 16, THICK_WALL]) base_exit += hole()(translate( [screw_xuin + THICK_WALL * 0.5, THICK_WALL, 0])(cube([58 - 2 * (screw_xuin + THICK_WALL * 0.5), 12, THICK_WALL]))) xula_base += back(16)(base_exit) return xula_base
def createlogo(): """createlogo Openscad cannot handle the Storm font. To mitigate, a vector image of the storm font is converted to PNG via Inkscape. The PNG image is linearly extruded and converted to a STL. This STL is imported by this function, to create the logo. """ # TODO: move Python converter for logo to here # LOGO bounding box x = 234 , y = 26, z = 1 # scaled to x = 120, y = 13 x_bound = 120 + THICK_WALL * 2 y_bound = 13 + THICK_WALL * 2 # TODO: should throw error !! you removed logo logo = scale([0.5, 0.5, 1])(import_stl('hexastorm.stl')) logo = None # openscad cannot handle minkowski on hexastorm logo # logo_mink = up(1)(minkowski()(cylinder(r=0.5, h=1), logo)) result = translate([-0.5 * x_bound, -0.5 * y_bound, 0])(cube( [x_bound, y_bound, 1])) - hole()(mirror([0, 1, 0])(logo)) result = scale([1, 1, HEIGHT_TOP - THICK_WALL ])(translate([0.5 * x_bound, 0.5 * y_bound, 0])(result)) result = up(HEIGHT_TOP - THICK_WALL)(cube([x_bound, y_bound, 1])) # TODO: Openscad can create a preview but does not render the logo, # at the moment we resort to # modiefs in blender return result
def ueyeholder(): """ueyeholder creates a holder for an ueye camera A holder for an uEye camera to view upward. The uEye camera cannot look upward due its connector on the back. This design solves this problem via a cubical enclosure. """ #### UEYE cubical dimension margin = 0.3 size_x = 34 # mm size_y = 32 # mm size_z = 34.6 # mm ### UEYE screw screw_d = 3 # mm screw_z = 30.4 # sep z-direction screw_z -= screw_d screw_ytop = 19.8 # mm screw_ytop -= screw_d screw_ybottom = 21.8 screw_ybottom -= screw_d screw_zoff = 1.3 + screw_d / 2 connector_z = 18 # mm connector_x = 16.2 # mm #### # box # camera is pushed in from bottom holder = cube([ size_x + 2 * THCKW + margin, size_y + 2 * THCKW + margin, size_z + THCKW + connector_z + margin ]) holder -= translate([THCKW, THCKW, THCKW])(cube([ size_x + margin, size_y + margin, size_z + THCKW + connector_z + margin ])) # 4 screw holes socket = cylinder(h=size_x + 2 * THCKW, r=screw_d / 2, segments=SGM) socket = rotate([0, 90, 0])(socket) sockets = socket + forward(screw_ybottom)(socket) temp = (screw_ytop - screw_ybottom) * 0.5 sockets += up(screw_z)(back(temp)(socket) + forward(screw_ytop - temp)(socket)) # substraction prep sockets holder -= translate([ 0, (size_y - screw_ybottom) * 0.5 + THCKW + 0.5 * margin, screw_zoff + THCKW + connector_z + 0.5 * margin ])(sockets) # space connector holder -= translate([THCKW + 0.5 * (size_x - connector_x), 0, THCKW])(cube( [connector_x, THCKW, size_z + THCKW + connector_z])) return holder
def mc_details(event): """ append MC details to array. Now only: - MC impact point on ground :param event: :return: """ # add MC core x and y cross = text(text="+", size=5000) cross = cross + translate([1000, 1000, 0])(text(text="MC", size=1000)) cross = color([1, 0, 0])(linear_extrude(200)(cross)) cross = translate( [event.mc.core_x.to('cm').value, event.mc.core_y.to('cm').value, 0])(cross) return cross
def lasershim(height): """lasershim This is a shim which can be used to pad. The base of the shim is in the XY plane at quadrant 1. One corner is at the origin. The width is parallel to the x-axis. The shim can be used if the laserbase is not correctly alligned. The laser was provided by Odic Force, productid OFL510-1. param: height: defines height shim [mm] """ # PARAMETER xdisp = 48.5 # [mm], x-displacement screw ydisp = 16 # [mm], y-displacement screws r_shaft = 2 + 0.5 # [mm], shaft radius screws length = 75 # [mm], x-direction length laser width = 30 # [mm], y-direction width laser screw_offst = 7 # [mm], screw offset +x-edge # MAXIMAL MATERIAL BASE base = cube([length, width, height]) # screw holes screws = cylinder(h=height, r=r_shaft) + right(xdisp)(cylinder(h=height, r=r_shaft)) spiegel = forward(ydisp / 2)(mirror([0, 1, 0])(back(ydisp / 2)(screws))) screws += spiegel # create holes base -= translate([length - xdisp - screw_offst, (width - ydisp) / 2, 0])(screws) return base
def rot_arrow(radius, angle_init, angle_end, label, text_flip=False): """ Create curved arrow as 1 degree-step cylinders with a cone at the end ==> arrow Add also a label in the middle of the arrox as a text. :param radius: radius is in cm :param angle_init: in degrees :param angle_end: in degrees :param label: label to be given to the arrow :param text_flip: rotate text by 180 degrees :return: arrow example: arr_curved = color([1, 0, 0])(rot_arrow(8000, az.to('deg').value,0, label='AZ')) array.add(arr_curved) """ curved_arrow = union() point = cylinder(r1=radius / 15, r2=0, h=radius / 8) point = rotate([0, -90, -90])(point) if angle_end > angle_init: angles = np.deg2rad(np.arange(angle_init, angle_end, 1))[1:-5] x = radius * np.cos(angles) y = radius * np.sin(angles) point = rotate([0, 0, np.rad2deg(angles[-2])])(point) elif angle_end < angle_init: angles = np.deg2rad(np.arange(angle_init, angle_end, -1))[:-5] x = radius * np.cos(angles) y = radius * np.sin(angles) point = rotate([0, 0, np.rad2deg(angles[-2]) + 180])(point) curved_body = arco(x, y, radius / 30) point = translate([x[-2], y[-2], 0])(point) curved_arrow.add(point) curved_arrow.add(curved_body) testo = linear_extrude(radius / 30)(text(label, size=radius / 5)) index = x.size // 3 * 2 testo = rotate([0, 0, np.rad2deg(angles[index])])(testo) if text_flip: testo = rotate([0, 0, 180])(testo) testo = translate( [0.15 * len(label) * x[index], 0.15 * len(label) * y[index], 0])(testo) testo = translate([1.1 * x[index], 1.1 * y[index], 0])(testo) curved_arrow.add(testo) return curved_arrow
def polygonshim(height): """polygonshim The polygon shim is located in first quadrant of the XY plane. A corner is at the origin. The width is parallel to the y-axis. The shim can be used to align the polygon. The shim was designed for polygon mirror Motor aficio 1018 G029-196. :param height: height shim """ # BASE: length = 68 # mm [y-direction] width = 48 # mm [x-direction] r_shaft = 2 # mm shaft radius slot_width = 2 # width slot base = cube([width / 2, length, height]) def slot(radius, height, width): """slot openscad styled vertically oriented printable slot origin formed by the center of the left circle :param radius: the radius of the top of the screw :param height: the height of the slot :param width: the width of the slot, i.e. distance between radii """ cyl = cylinder(h=height, r=radius) outer = hull()(cyl, right(width)(cyl)) return outer # create 2 screw slots simple_slot = slot(r_shaft, height, slot_width) base -= translate([3.1, length - 4, 0])(simple_slot) base -= translate([3.2, 4 + 1.29, 0])(rotate([0, 0, -50])(simple_slot)) # create hole for polygon rotation axis base -= translate([24, 24, 0])(cylinder(h=height, r=10)) # create hole for polygon lock base -= translate([24 - 7.5, 0, 0])(cube([15, 10, height])) # mirror and add to original spiegelold = right(width)(mirror([1, 0, 0])(base)) base += spiegelold return base
def topbox(down, logo): """topbox constructs the top part of the box :param down: if true downward ray box created :param logo: if true logo is generated, logo slows rendering """ top = cube([LENGTH_TOP, WIDTH_TOP, THICK_WALL]) # 4 screws used, 2 was insufficient screw_fixout = 3.5 # mm (radius) screw_fixin = 2 # TODO: connect to holesize threaded inserti screw_toph = 5 cyl = screw(screw_fixout, screw_toph, screw_fixin, HEIGHT_TOP) top += translate([SCREW_FIXOFFST, SCREW_FIXOFFST, 0])(cyl) top += translate([LENGTH_TOP - SCREW_FIXOFFST, SCREW_FIXOFFST, 0])(cyl) top += translate( [LENGTH_TOP - SCREW_FIXOFFST, WIDTH_TOP - SCREW_FIXOFFST, 0])(cyl) top += translate([SCREW_FIXOFFST, WIDTH_TOP - SCREW_FIXOFFST, 0])(cyl) # sliding should be prevented with 4 protrusion, # 1 is logo and 3 other are knobs x_knob = cube([THICK_WALL, THICK_WALL * 3, HEIGHT_TOP - THICK_WALL]) x_knobs = translate( [THICK_WALL, WIDTH_TOP / 2 - 1, THICK_WALL])(x_knob) + translate([ LENGTH_TOP - 2 * THICK_WALL, WIDTH_TOP / 2 - 1, THICK_WALL ])(x_knob) y_knob = cube([THICK_WALL * 3, THICK_WALL, HEIGHT_TOP - THICK_WALL]) y_knobs = translate([LENGTH_TOP * 0.25, 0, THICK_WALL ])(forward(THICK_WALL)(y_knob) + forward(WIDTH_TOP - 2 * THICK_WALL)(y_knob)) top += y_knobs + x_knobs # LOGO slows down render, should be turned off when developing if logo: top += translate([ 0.5 * (LENGTH_TOP - (120 + THICK_WALL * 2)), WIDTH_TOP - THICK_WALL - (13 + THICK_WALL * 2), 0 ])(createlogo()) if not down: laser_y = 24 + 2 * THICK_WALL top -= translate( [75 + 10 + 48 + THICK_WALL + 10, laser_y - 0.5 * 8, 0])(cube([20, 8, THICK_WALL])) # FIX FOR BOX orientation top = rotate([0, 0, 180])(mirror([0, 1, 0])(rotate([0, 180, 0])(top))) return top
def circle_trans(center, radius, height): """ Draw translated circle at *center* and with *radius* :param center: tuple (x,y) for the center :param radius: radius of pixel element :param height: height of camera to be plotted :return: the translated circle """ (center_x, center_y) = center return translate([center_x, center_y])(cylinder(r=radius, h=height, segments=6))
def threadedinsert(flip_x, thick, holesize, length): """insert heatpress insert Heatpress can be inserted from the top. The screw should also be inserted from the top. The screw is centered at the origin. The wall thickness are specified by; http://uk.farnell.com/tr-fastenings-brass-inserts-for-plastics The length and hole size should be obtained from the sheet. this threaded is supported by a triangle and can be mirrored in the x-direction :param flip_x: flip insert in, origin shifted back :param length: length of the insert :param holesize: diameter of the hole """ # NOTE: other options # -sliding ; this results in a cable collision # -press insert; more recommended for photopolymer parts, less permanent # -bolt printed inside; requires print pause, not useful in production # -magnet; magnets are dangerous for electronics x_extent = holesize + 2 * thick y_extent = holesize / 2 + thick + SCREW_FIXOFFST base = cube([x_extent, y_extent, length]) triangle = polygon([[0, 0], [y_extent, y_extent], [0, y_extent]]) prism = linear_extrude(x_extent)(triangle) support = translate([x_extent, y_extent, 0])(rotate([90, 0, -90])(prism)) base = support + up(y_extent)(base) base -= translate([holesize / 2 + thick, holesize / 2 + thick, 0])(cylinder(h=length + y_extent, r=holesize / 2, segments=30)) # changed orientation to simplify placement base = down(y_extent + length)(base) # center origin at Z-axis base = translate( [-x_extent / 2, -SCREW_FIXOFFST + thick + holesize / 2, 0])(base) if flip_x: base = rotate([0, 0, 180])(base) return base
def mirror_plane_creator(tel_type, radius): """ Create a fake mirror plane and hole at center :param tel_type: select mirror plane type: 'LST', 'MST' :param radius: radius of the mirror plane. (e.g.: LST is 11,50 m == 23/2 m) :return: mirror_plane with """ if tel_type == 'LST': mirror_plane = difference() mirror_plane.add(color([1, 0, 0])(sphere(radius))) mirror_plane.add( translate([0, 0, 1.6 * radius])(color([0, 0, 1, 0.7])(sphere(radius * 2)))) cal_box = color([1, 0, 0])(translate([0, 0, -radius])(cylinder(r=radius / 6, h=radius * 2))) mirror_plane.add(cal_box) elif tel_type == 'MST': # first create a big sphere (to have a big curvature radius), then cut a hole for the calbox spheres = difference() spheres.add(color([1, 0, 0])(sphere(3 * radius))) spheres.add(color([0, 0, 1, 0.6])(sphere(3 * radius * 0.96))) cal_box = color([1, 0, 0])(translate([0, 0, -3 * radius])(cylinder(r=radius / 6, h=radius * 2))) spheres.add(cal_box) # then select only one part of the full sphere mirror_plane = intersection() mirror_plane.add(spheres) sel_region = translate([0, 0, -3 * radius])(cylinder(r=radius, h=radius)) mirror_plane.add(sel_region) # finally translate everything at zero mirror_plane = translate([0, 0, 3 * radius])(mirror_plane) return mirror_plane
def laserbase(laserheight): """laserabase creates the basis for the laser with ventilation wall The laserbase is in the XY plane at quadrant 1. One corner is at the origin. The width is parallel to the x-axis. The laser was provided by Odic Force, productid OFL510-1. The padheight is laser height- 16.5 The laserbundle travels in the +x direction and departs from the center, that is 15 mm. param: laserheight: the desired height of the laser """ # The laser tube is at 8 mm from bottom. # The laser tube has a diameter of 17 mm # The laser is at 8 + 17 * 0.5 - 1 = 16.5 mm (shim of 1 mm needed) # The laser base is 30x60 mm, which was made # 30x75 mm to make room for the ventilator # PARAMETERS height = laserheight - 15.5 # [mm], xdisp = 48.5 # [mm], x-displacement screws ydisp = 16 # [mm], y-displacement screws r_shaft = 2 # [mm], shaft radius screws h_head = 5 # [mm], height shaft head r_head = 3.5 # [mm], top radius screws tspile = 4 # [mm], y-thickness ventilation spile hspile = 25 # [mm], height ventilation spile length = 75 # [mm], x-direction length laser width = 30 # [mm], y-direction width laser screw_offst = 7 # [mm], screw offset +x-edge # MINIMAL MATERIAL BASE screws = screw(r_head, h_head, r_shaft, height) + right(xdisp)(screw( r_head, h_head, r_shaft, height)) spiegel = forward(ydisp / 2)(mirror([0, 1, 0])(back(ydisp / 2)(screws))) screws += spiegel base = translate([length - xdisp - screw_offst, (width - ydisp) / 2, 0])(screws) # ventilation wall # spile spile = up(height)(cube([THICK_WALL, tspile, hspile])) nofspiles = ceil((width) / (tspile * 2)) # shift base base = right(THICK_WALL)(base) # add wall base += cube([THICK_WALL, width, HEIGHT_WALL]) # create pockets for i in range(0, nofspiles): base -= hole()(forward(i * 2 * tspile + THICK_WALL)(spile)) return base
def arrow(heigth, tail, label, rotation=(0, 0, 0)): """ Create arrow with rotation. Default is VERTICAL, along Z-axis :param heigth: (float) height :param tail: (tuple) position of tail. :param rotation: (tuple) rotation along x, y and z axis :param label: something converted to string to put as label on axis :return: arrow with label TODO: add rotation from rotation function and not with the *rotate* in *solid* """ arrow_inst = union() arrow_inst.add(cylinder(r=heigth / 20, h=heigth)) arrow_inst.add( translate([0, 0, heigth])(cylinder(r1=heigth / 10, r2=0, h=heigth / 8))) arrow_inst.add( translate([0, 0, heigth * 1.2])(rotate([90, -90, 0])(linear_extrude( heigth / 40)(text(text=label, size=heigth / 5, font="Cantarell:style=Bold"))))) arrow_inst = rotate(list(rotation))(arrow_inst) arrow_inst = translate(list(tail))(arrow_inst) return arrow_inst
def ground_grid(event, tel_pos=False): """ Return the telescopes positions in the GroundFrame and plot on ground :param event: input event selected from simtel :param tel_pos: (bool) if True, plot the telescopes as spheres :return: """ alt = event.mcheader.run_array_direction[1] az = event.mcheader.run_array_direction[0] array_pointing = HorizonFrame(alt=alt, az=az) ground_coordinates = GroundFrame(x=event.inst.subarray.tel_coords.x, y=event.inst.subarray.tel_coords.y, z=event.inst.subarray.tel_coords.z, pointing_direction=array_pointing) grid_unit = 20000 # in centimeters ground_system = union() if tel_pos: # for i in range(ground_coordinates.x.size): for i in range(50): coords = [ 100 * ground_coordinates.x[i].value, 100 * ground_coordinates.y[i].value, 100 * ground_coordinates.z[i].value ] position = translate(coords)(color([0, 0, 1])(sphere(r=800))) ground_system.add(position) grid = grid_plane( grid_unit=grid_unit, count=2 * int(100 * np.max(np.abs(ground_coordinates.x.value)) / grid_unit), line_weight=200, plane='xy') grid = color([0, 0, 1, 0.5])(grid) ground_system.add(grid) # SYSTEM + ARROW ref_arr = ref_arrow_3d(8000, origin=(1000, 1000, 0), label={ 'x': "x_gnd = NORTH", 'y': "y_gnd = WEST", 'z': "z_gnd" }) ground_system = ground_system + ref_arr return ground_system
def arco(x, y, radius): """ Create curve from set of point. Each point is connected with a cylinder The curve must be 2D, because I want to perform only rotations along one axis. Connect P(x[i],y[i]) and P(x[i+1], y[y+1]) :param x: np.array with the x points :param y: np.array with the "heights" :param radius: dimension cylinder :return: curve """ arco_sum = union() for i in range(x.size - 1): begin = (x[i], y[i]) end = (x[i + 1], y[i + 1]) angle = np.rad2deg(np.arctan((y[i] - y[i + 1]) / (x[i] - x[i + 1]))) heigth = length(end, begin) new_cylinder = multmatrix(m=rotation(90, 'y'))(cylinder(h=heigth, r=radius)) new_cylinder = multmatrix(m=rotation(angle, 'z'))(new_cylinder) new_cylinder = translate(list(begin))(new_cylinder) arco_sum.add(new_cylinder) return arco_sum
def struct_spider(height, radius_square_low, radius_square_top): """ Create structure for MST and SST camera with one mirror (FOR THE MOMENT). :param height: height between mirror plane and camera plane :param radius_square_low: radius in the lower part (mirror plane) :param radius_square_top: radius in the upper part (camera plane) :return: 4 spider and camera structure """ structure = union() sides = 4 delta_angles = 360. / sides incl_angle = 90 - np.rad2deg( np.arctan(height / (radius_square_low - radius_square_top))) spider = cylinder(h=height, r=20) spider = multmatrix(m=rotation(incl_angle, 'y'))(spider) spider = translate([-radius_square_low + 25, 0, 110])(spider) for i in range(sides): structure = multmatrix(m=rotation(delta_angles, 'z'))(structure) structure.add(spider) structure = multmatrix(m=rotation(45, 'z'))(structure) return structure
def telescope(tel_description, camera_display_bool, pointing, origin, tel_num='0', ref_camera=True, ref_tel=False, sim_to_real=False): """ Create telescope. Implemented only 'LST' by now. Everything is somehow in centimeters. :param tel_description: string for telescope type. 'LST', 'MST', ecc. :param camera_display_bool: input from camera_event.py loaded another event. :param pointing: dictionary for pointing directions in degrees above horizon, {'alt': val, 'az': val} :param origin: (x, y, z) position of the telescope :param tel_num: (int) Telescope ID to be plotted with the telescope :param ref_camera: (bool) create ref frame on camera :param ref_tel: (bool) create ref frame at the center of the telescope...TODO: needed? :param sim_to_real: (bool) WITHOUT THIS THE CAMERA IS IN CTAPIPE VISUALIZATION != REAL WORLD :return: geometry for the telescope. TODO: create real substructure for telescope? """ DC_list = ['LSTCam', 'FlashCam', 'NectarCam', 'DigiCam'] SC_list = ['SCTCam', 'ASTRICam', 'CHEC'] # unpack camera_display values camera_display = camera_display_bool[0] bool_trig = camera_display_bool[1] if bool_trig: color_trig = [1, 0, 0] else: color_trig = [0, 1, 0] telescope_struct = union() tel_type = tel_description.split(':')[0] camera_name = tel_description.split(':')[1] if camera_name in DC_list: if tel_type == 'LST': # create mirror plane mirror_plane = mirror_plane_creator(tel_type=tel_type, radius=1150) # define arch arch = union() x_arco = np.linspace(-2200 / 2, 2200 / 2, 50) y_arco = 4 / 2300 * x_arco**2 arch_struct = color([1, 0, 0])(arco(x_arco, y_arco, 30)) arch_struct = multmatrix(m=rotation(-90, 'y'))(arch_struct) arch_struct = multmatrix(m=rotation(-90, 'x'))(arch_struct) arch.add(arch_struct) arch = translate([0, 0, np.max(y_arco) - 200])(arch) # append camera to arch camera_frame = cube([400, 400, 190], center=True) if sim_to_real: camera_display = multmatrix( m=rotation(90, 'z'))(camera_display) camera_display = multmatrix( m=rotation(180, 'x'))(camera_display) # check for arrows in reference frame camera_frame = camera_frame + camera_display if ref_camera: arrow_camera = ref_arrow_2d(500, label={ 'x': "x_cam", 'y': "y_cam" }, origin=(0, 0)) arrow_camera = multmatrix(m=rotation(180, 'x'))(arrow_camera) camera_frame = camera_frame + arrow_camera # ADD camera_frame and camera display to arch structure arch.add(camera_frame) # put together arch and mirror plane telescope_struct.add(arch) telescope_struct.add(mirror_plane) # telescope_struct = translate([0, 0, 450])(telescope_struct) elif tel_type == 'MST': radius = 600 height = 1800 ratio_cam = 2 mirror_plane = mirror_plane_creator(tel_type=tel_type, radius=radius) telescope_struct.add(mirror_plane) # add the long spiders to the structure structure = struct_spider(height, radius, radius / ratio_cam) # create camera structure with ref arrow and overplot the event side_cam = 2 * (radius / ratio_cam) / np.sqrt(2) camera_frame = cube([side_cam, side_cam, 100], center=True) if sim_to_real: camera_display = multmatrix( m=rotation(90, 'z'))(camera_display) camera_display = multmatrix( m=rotation(180, 'x'))(camera_display) camera_frame = camera_frame + camera_display # check for arrows in reference frame if ref_camera: arrow_camera = ref_arrow_2d(500, label={ 'x': "x_cam", 'y': "y_cam" }, origin=(0, 0)) arrow_camera = multmatrix(m=rotation(180, 'x'))(arrow_camera) camera_frame = camera_frame + arrow_camera camera_frame = translate([0, 0, 110])(camera_frame) # raise to top of telescope, minus 30 cm in order to look nicer camera_frame = translate([0, 0, height - 30])(camera_frame) structure = structure + camera_frame # add structure, camera frame and camera on the telescope structure telescope_struct.add(structure) # telescope_struct = translate([0, 0, -150])(telescope_struct) elif tel_type == 'SST-1M': # TODO: CREATE MODEL FOR SST 1-M: re-use the MST print("sst") pass elif camera_name in SC_list: pass if tel_type == 'MST-SCT': print("MST-SCT") elif tel_type == 'SST:ASTRI': print("SST:ASTRI") elif tel_type == 'SST-GCT': print("SST-GCT") else: print("NO tel_name FOUND") sys.exit() telescope_struct = multmatrix(m=rotation(-90, 'z'))(telescope_struct) # rotate to pointing. First move in ALTITUDE and then in AZIMUTH zen = 90 - pointing['alt'].value az = pointing['az'].value telescope_struct = multmatrix(m=rotation(zen, 'y'))(telescope_struct) telescope_struct = multmatrix(m=rotation(-az, 'z'))(telescope_struct) # ADD TELESCOPE ID print(tel_num, tel_type) tel_number = color(color_trig)(linear_extrude(100)(text(text=str(tel_num), size=10000, spacing=0.1))) tel_number = rotate((0, 0, az + 90))(tel_number) tel_number = translate((origin[0] - 700, origin[1] - 700, 0))(tel_number) telescope_struct = translate(list(origin))(telescope_struct) telescope_struct = telescope_struct + tel_number return telescope_struct
def polygonbase(laserheight): """polygonbase defines the base of a polygon The polygon base is located in the first quadrant of the XY plane. A corner is at the origin. The width is parallel the y-axis. The laser bundle is at 24 mm in the y-direction and the laser bundle should be between [10.65, 13.65] mm in the z-direction. The height of the base is laserheight - 12.15 mm. The height of the base should be at least 7.2 mm. The polygon motor result in a protrusion. The laser is directed in the +x-direction. The length is oriented along y-axis, 21000 RPM polygon base could be rotated if laserbase is made smaller. New polygon base at 24000 RPM seems to be harder to rotate. The polygon rotates clockwise. :param laserheight: the desired height of the laser, [mm] """ # TODO: the polygon is larger than its, it has a negative x- and y-extent # BASE: length = 68 # [mm], y-direction width = 48 # [mm], x-direction height = laserheight - 12.5 # [mm] r_shaft = 2 # [mm], shaft radius r_head = 3.5 # [mm], head radius h_head = 5 # [mm], head insert slot_width = 2 # [mm], width slot def slot(r_head, h_head, r_shaft, width, height): """slot openscad styled vertically oriented printable slot origin formed by the center of left circle :param r_head: the radius of the top of the screw, [mm] :param h_head: the height of the top of the screw, [mm] :param r_shaft: the radius of the shaft of the screw, [mm] :param width: the width of the slot, [mm] :param height: the height of the slot, [mm] """ h_shaft = height - h_head - (r_head - r_shaft) head = cylinder(h=h_head, r=r_head, segments=30) # 45 degrees cone for printability cone = up(h_head)(cylinder(h=r_head - r_shaft, r1=r_head, r2=r_shaft, segments=30)) shaft = up(h_head + (r_head - r_shaft))(cylinder(h=h_shaft, r=r_shaft, segments=30)) cyl = head + cone + shaft inner = hull()(cyl, right(width)(cyl)) cyl = cylinder(h=height, r=r_head + THICK_WALL) outer = hull()(cyl, right(width)(cyl)) slot = outer - hole()(inner) return slot wall_slot = slot(r_head, h_head, r_shaft, slot_width, height) base = translate([3.1, length - 4, 0])(wall_slot) base += translate([3.2, 4 + 1.29, 0])(rotate([0, 0, -50])(wall_slot)) spiegel = right(width)(mirror([1, 0, 0])(base)) base += spiegel return base
def mirrormount(down, laserheight): """mirrormount A 25 mm x 25 mm square and 2 mm thick first sided mirror is used to refract the ray downward or upward. The thickness is in the +x-direction. This mirror is tilted at a 45 degrees and is positioned by a holder. The holder is put in place via two pillars. A photodiode mount is placed into these pillars to detect the laser motion. It is important that the photodiode is at the correct height The photodiode_height is LASER_HEIGHT-2.5, to ensure the laser hits the photodiode at its center. :param down: if true downward refraction, if false upward refraction :param laserheight: height laser bundle, [mm] """ width_mirror = 25 # [mm] # thickness y+ pillar, y- pillar is insert+THICK_wall tpillar_left = 14 # [mm] insert_mirror = 5 # [mm] thick_mirror = 2 # [mm] # height_mirror < photodiode_height height_mirror = laserheight - 6 # [mm] # 4.5 determined via felix printed box photodiode_height = laserheight - 4.5 # [mm] cable_guide = 2 # [mm] # sensor width with cables is 5.6 (measurement @diode) sensor_width = 2 # [mm] # sensor height is 4 (measurement @ photodiode) sensor_height = 4.5 # [mm] sensor_insert = 2 # [mm] diode thickness i 2 @ measured # margin is needed for FFF printer margin = 0.5 # [mm] # defines the thickness of the holder thick = 1.3 # [mm] # offset constraint set by upward proj. due to cable collision possibility offset = 19 # [mm] offset sensor pole x_width = 0.5 * sqrt(2) * (thick_mirror + margin + 2 * thick) # TODO: xbound seems to be an y_bound x_bound = 0.5 * sqrt(2) * (2 * thick + width_mirror + margin) + x_width y_bound = offset + THICK_WALL + sensor_insert holder = cube([ thick_mirror + margin + 2 * thick, light_hole + THICK_WALL + insert_mirror + tpillar_left, width_mirror + margin + 2 * thick ]) holder_inner = translate([thick, 0, thick])(cube([ thick_mirror + margin, light_hole + tpillar_left + insert_mirror, width_mirror + margin ])) # the holder can contain left over of filament. # To remove these left over a cleaning hole is needed. holder_inner += translate([thick, 0, thick + width_mirror + margin])(cube( [thick_mirror + margin, tpillar_left - THICK_WALL, thick])) holder -= hole()(holder_inner) # up mirror holder = up(height_mirror)(rotate([0, 45, 0])(holder)) # pillars # light exit has a width of light_hole # pillars are next to this exit point and have a width of tpillar_left, # and THICK_WALL + insert_mirror mount_mirror = cube([ x_width, light_hole + tpillar_left + THICK_WALL + insert_mirror, height_mirror ]) mount_mirror += holder # create pocket for light 2x is for certainty mount_mirror -= forward(tpillar_left)(cube( [2 * width_mirror, light_hole, 2 * width_mirror])) if not down: mount_mirror = right(x_bound)(mirror([1, 0, 0])(mount_mirror)) # add mount photodiode # the photodiode is at height photodiode_height mm # the cable guides are cable_guide mm thick, the pins of the photodiode # are sensor width displaced, the photodiode is sensor height tall # the photodiode sensor insert is sensor_insert, the wall between light # exit and sensor is fixed at 1 mm, kept small to get maximum out of light # path. The top has a three time thickness, to create a connection between # mirror and pole enclosure = cube([ THICK_WALL + sensor_insert, cable_guide * 2 + sensor_width + THICK_WALL + 1, sensor_height + photodiode_height + 2 * THICK_WALL ]) photodiode = cube([ sensor_insert + THICK_WALL, cable_guide * 2 + sensor_width, sensor_height ]) # substract central pillar photodiode -= translate([0, cable_guide, 0 ])(cube([THICK_WALL, sensor_width, sensor_height])) # combine pole with photodiode housing pole = enclosure - hole()(translate([0, 1, photodiode_height])(photodiode)) combined = mount_mirror + translate([offset, tpillar_left + light_hole, 0 ])(pole) # a trafo is executed to simplify positioning; # light should be centered at y=0 combined = translate([y_bound, tpillar_left + 0.5 * light_hole, 0])(mirror([1, 0, 0])(mirror([0, 1, 0])(combined))) # add tie-wrap # TODO: remove custom parameters fasten = translate([9, tpillar_left + 9, 0])(cable_fasten(TIE_HEIGHT, TIE_WIDTH, THICK_WALL, True)) combined += fasten return combined
def tilted_grid(event, tel_pos=False, zen_az_arrows=False): """ Return the telescopes positions in the TiltedGroundFrame and plot them according to azimuth and zenith of simulation :param event: event selected from simtel :param tel_pos: (bool) If True, plot the telescopes as spheres :param zen_az_arrows: plot curved arrows for ZEN and AZ in titled ref frame :return: """ alt = event.mcheader.run_array_direction[1] az = event.mcheader.run_array_direction[0] array_pointing = HorizonFrame(alt=alt, az=az) ground_coordinates = GroundFrame( x=event.inst.subarray.tel_coords.x, y=event.inst.subarray.tel_coords.y, z=event.inst.subarray.tel_coords.z, # *0+15.0*u.m, pointing_direction=array_pointing) tilted_system = TiltedGroundFrame(pointing_direction=array_pointing) tilted = ground_coordinates.transform_to(tilted_system) grid_unit = 20000 # in centimeters tilted_system = union() # ADD TELESCOPES AS SPHERES if tel_pos: # for i in range(tilted.x.size): for i in range(50): coords = [100 * tilted.x[i].value, 100 * tilted.y[i].value] position = translate(coords)(color([1, 0, 0])(sphere(r=800))) tilted_system.add(position) # add GRID grid_tilted = grid_plane( grid_unit=grid_unit, count=2 * int(100 * np.max(np.abs(ground_coordinates.x.value)) / grid_unit), line_weight=200, plane='xy') grid_tilted = color([1, 0, 0, 0.5])(grid_tilted) grid_tilted = grid_tilted + ref_arrow_2d( 8000, label={ 'x': "x_tilted", 'y': "y_tilted" }, origin=(0, 0)) tilted_system = rotate([0, 90 - alt.to('deg').value, az.to('deg').value])(tilted_system) tilted_system.add(grid_tilted) arr_curved_az = color([1, 1, 0])(rot_arrow(8000, az.to('deg').value, 0, label='AZ')) tilted_system.add(arr_curved_az) arr_curved_alt = color([1, 0, 1])(rot_arrow(8000, 0, 90 - alt.to('deg').value, label='ZEN')) arr_curved_alt = rotate([90, 0, 0])(arr_curved_alt) tilted_system.add(arr_curved_alt) return tilted_system
def assembly(): return sp.union()( sp.color('red')(spu.translate(pot_offset)(sp.rotate( (0, 90, 0))(sp.rotate((0, 0, -110. + angle))(trigger.volume())))), sp.color('green')(mount.volume()), )
def hscrew(head_r, head_height, shaft_r, length): """horizontal screw a horizontal screw consists out of three parts * a cylinder in the wall * possible a continuation of the head insert * shaft piece algorithm ensures it is printable param head_height: height head screw param head_r: radius head screw param shaft_r: radius shaft screw param lenght: lenght screw """ # cylinder in the wall if head_height > THICK_WALL: shift = head_height - THICK_WALL cyl_wall = cylinder(h=THICK_WALL, r=head_r + THICK_WALL, segments=30) cyl_mid = up(THICK_WALL)(cylinder(h=shift, r1=head_r + shift + THICK_WALL, r2=head_r + THICK_WALL, segments=30)) hscrew = cyl_mid + cyl_wall else: hscrew = cylinder(h=head_height, r=head_r + THICK_WALL, segments=30) # two cases: if length - head_height > head_height: shift = length - THICK_WALL cyl_shaft = up(head_height)(cylinder(h=shift, r1=shaft_r + shift + THICK_WALL, r2=shaft_r + THICK_WALL, segments=30)) else: cyl_shaft = up(head_height)(cylinder(h=length - head_height, r1=head_r + THICK_WALL, r2=shaft_r + THICK_WALL, segments=30)) hscrew += cyl_shaft hscrew = rotate([90, 0, 0])(hscrew) # gravity only one direction --> minimize in this direction # shaft field = cube([ 2 * (shaft_r + THICK_WALL), length - head_height, length + 2 * shaft_r + THICK_WALL ]) field = translate([-shaft_r - THICK_WALL, -length, -(length + shaft_r)])(field) sscrew = hscrew * field # top if head_height > THICK_WALL: field = cube([ 2 * (head_r + THICK_WALL), head_height, length + 2 * head_r + THICK_WALL ]) field = translate( [-head_r - THICK_WALL, -head_height, -(length + head_r)]) (field) tscrew = hscrew * field else: tscrew = rotate([90, 0, 0])(cylinder(h=head_height, r=head_r + THICK_WALL, segments=30)) hscrew = tscrew + sscrew cyl_top = down(0.1)(cylinder(h=head_height + 0.1, r=head_r, segments=30)) cyl_shaft = up(head_height - 0.1)(cylinder(h=length - head_height + 0.1, r=shaft_r, segments=30)) interior = rotate([90, 0, 0])(cyl_shaft + cyl_top) omg = hscrew - hole()(interior) # OMG HOLE is buggy; hscrew-=hole()(interior) does not always work return omg
def generate_skyline_stl(username, year, running_matrix): """ Some code of this function is from https://github.com/felixgomez/gitlab-skyline """ max_run_distance = max(running_matrix) total_run_distance = sum(running_matrix) base_top_width = 23 base_width = 30 base_length = 150 base_height = 10 max_length_run_distance = 40 bar_base_dimension = 2.5 base_top_offset = (base_width - base_top_width) / 2 face_angle = math.degrees(math.atan(base_height / base_top_offset)) base_points = [ [0, 0, 0], [base_length, 0, 0], [base_length, base_width, 0], [0, base_width, 0], [base_top_offset, base_top_offset, base_height], [base_length - base_top_offset, base_top_offset, base_height], [base_length - base_top_offset, base_width - base_top_offset, base_height], [base_top_offset, base_width - base_top_offset, base_height], ] base_faces = [ [0, 1, 2, 3], # bottom [4, 5, 1, 0], # front [7, 6, 5, 4], # top [5, 6, 2, 1], # right [6, 7, 3, 2], # back [7, 4, 0, 3], # left ] base_scad = polyhedron(points=base_points, faces=base_faces) year_scad = rotate([face_angle, 0, 0])( translate( [ base_length - base_length / 5, base_height / 2 - base_top_offset / 2 - 1, -1.5, ] )(linear_extrude(height=2)(text(str(year), 6))) ) user_scad = rotate([face_angle, 0, 0])( translate([base_length / 4, base_height / 2 - base_top_offset / 2, -1.5])( linear_extrude(height=2)(text("@" + username, 5)) ) ) total_scad = rotate([face_angle, 0, 0])( translate( [ base_length - base_length / 3 - 17, base_height / 2 - base_top_offset / 2, -1.5, ] )(linear_extrude(height=2)(text(str(round(total_run_distance, 1)) + " km", 5))) ) running_scad = rotate([face_angle, 0, 0])( translate([base_length / 12, base_height / 2 - base_top_offset / 2, -1])( linear_extrude(height=2)(text("Running", 5)) ) ) bars = None week_number = 1 for i in range(len(running_matrix)): day_number = i % 7 if day_number == 0: week_number += 1 if running_matrix[i] == 0: continue bar = translate( [ base_top_offset + 2.5 + (week_number - 1) * bar_base_dimension, base_top_offset + 2.5 + day_number * bar_base_dimension, base_height, ] )( cube( [ bar_base_dimension, bar_base_dimension, running_matrix[i] * max_length_run_distance / max_run_distance, ] ) ) if bars is None: bars = bar else: bars += bar scad_running_filename = "running_" + username + "_" + str(year) scad_skyline_object = base_scad - running_scad + user_scad + total_scad + year_scad if bars is not None: scad_skyline_object += bars scad_render_to_file(scad_skyline_object, scad_running_filename + ".scad") subprocess.run( [ "openscad", "-o", scad_running_filename + ".stl", scad_running_filename + ".scad", ] ) print("Generated STL file " + scad_running_filename + ".stl")
def draw_camera(event, itel, subarray, scale_cam=1.0, tail_cut_bool=False): """ Draw camera, either with or without an event. Take info from a simtel file. Make camera a list with the second element a boolean which knows if the camera has data after the cleaning. :param event: event selected from simtel file. :param itel: telescope ID from simtel file. Select only LST IDs for the moment :param subarray: subarray info from the simtel file. Needed for the description of the instrument :param scale_cam: scale the whole camera to see it better :param tail_cut_bool: (bool) decide whether to perform the tailcut :return: return camera object to plot on a telescope object """ camera_display = union() camera = subarray.tel[itel].camera if camera.cam_id == 'LSTCam': cam_height = 200 elif camera.cam_id == 'NectarCam' or camera.cam_id == 'FlashCam': cam_height = 120 print('plotting camera: ', camera.cam_id) x_pix_pos = 100 * camera.pix_x.value y_pix_pos = 100 * camera.pix_y.value # calculate pixel size and expand it a bit (1.1 scale) side = 1.1 * np.sqrt(((x_pix_pos[0] - x_pix_pos[1])**2 + (y_pix_pos[0] - y_pix_pos[1])**2)) / 2 data_after_cleaning = False # Perform tailcut cleaning on image pic_th = tail_cut[camera.cam_id][0] bound_th = tail_cut[camera.cam_id][1] image_cal = event.dl1.tel[itel].image[0] if tail_cut_bool: mask_tail = tailcuts_clean(camera, image_cal, picture_thresh=pic_th, boundary_thresh=bound_th, min_number_picture_neighbors=1) max_col = np.max(image_cal * mask_tail) # set boolean for trigger display on ground data_after_cleaning = np.sum(mask_tail) == 0 else: mask_tail = np.full(x_pix_pos.size, True) max_col = np.max(image_cal) # for the color plotting of the untriggered telescope try: image_cal = image_cal / max_col except RuntimeWarning: pass for i in range(x_pix_pos.size): # camera_display.add((hexagon((x_pix_pos[i],y_pix_pos[i]), side, 6))) colore = list(cmap(image_cal[i] * mask_tail[i])) center = (x_pix_pos[i] * scale_cam, y_pix_pos[i] * scale_cam) camera_display.add( color(colore)(circle_trans(center=center, radius=side * scale_cam, height=cam_height))) camera_display = camera_display.add( translate([0, 0, cam_height / 2])(ref_arrow_2d(400, label={ 'x': "x_sim", 'y': "y_sim" }, origin=(0, 0)))) camera_display = multmatrix(m=rotation(180, 'y'))(camera_display) camera_display = translate([0, 0, cam_height / 2])(camera_display) # return also the boolean for the cleaned image camera_display_arr = [camera_display, data_after_cleaning] return camera_display_arr
def onderkantbox(down): """onderkantbox constructs the bottom part of the box :param down: if true ray will be directed downward """ # construct a base # height wall ; earlier experiments ; 40 (bottom) + 25 (top) height = 65 base = cube([LENGTH_TOP, WIDTH_TOP, THICK_WALL + height]) base -= translate([THICK_WALL, THICK_WALL, THICK_WALL])(cube( [LENGTH_TOP - 2 * THICK_WALL, WIDTH_TOP - 2 * THICK_WALL, height])) # NOTE the order in which objects are placed is important # there can be coflicts between the stickit/panel mount and the # mirror mount # most likely this is due to the inner workings of the hole function # the polygon is the lowest part, for certainty its offset is set at # THICK_WALL laser_y = 24 (polygon) + 2 * THICK_WALL # as a result y-offset laserbase is laser_y - 0.5 * 30 (width laserbase) # the y-offset mirror base=laser_y-0.5*light_hole-THICK_WALL-INSERT_MIRROR # the x-offset of the laserbase is 0, # it comes with an integrated ventilation # the x-offset of the polygon is 75 (length_base) + 10 (laserlens) # the x-offset mirror is 75 + 10 + 48 (width polygon) + THICK WALL (safety # margin) TODO: for an unknown reason the stickit nead to be added first # other wise the photodiode mount will be effected # add stick base += translate([THICK_WALL + 90, WIDTH_TOP, 0])(xulaconnector()) # add laser base laser_y = 24 + 2 * THICK_WALL base += translate([0, laser_y - 0.5 * 30, 0])(laserbase(LASER_HEIGHT)) # add polygon 4 is space for square base += translate([75 + 10 + 4, 2 * THICK_WALL, 0])(polygonbase(LASER_HEIGHT)) # add mirror; y position is corrected for mirror_insert, # thick wall +8 (square) and light hole base += translate([75 + 10 + 48 + THICK_WALL + 8, laser_y, 0])(mirrormount(down, LASER_HEIGHT)) # add exits DC barrels r_barrel = 6.6 dcbarrel = rotate([90, 0, 0])(cylinder(r=r_barrel, h=2 * THICK_WALL, segments=30)) # NOTE: offset between DC barrels should # be larger than radius due to extent base -= translate([ THICK_WALL + r_barrel + 10, WIDTH_TOP, THICK_WALL + r_barrel * 2 ])(dcbarrel + up(2 * r_barrel + 4 + THICK_WALL)(dcbarrel)) # add exit for microusb (also has width of DC barrel) base -= translate( [LENGTH_TOP - THICK_WALL, WIDTH_TOP - 23.2, THICK_WALL + 11 + 23])(rotate([0, 0, 90])(dcbarrel)) # add mount belt mount_box = cube([10, 30, 50]) base -= translate( [LENGTH_TOP - THICK_WALL, WIDTH_TOP - 60, THICK_WALL + 15])(mount_box) base += translate( [LENGTH_TOP - THICK_WALL, WIDTH_TOP - 60, THICK_WALL + 15])(boxmount()) # you need to create room mirror if down: # TODO: remove manual fixed parameters # manual fix parameters; x 10 shift and y extent 20 base -= translate( [75 + 10 + 48 + THICK_WALL + 10, laser_y - 0.5 * light_hole, 0])(cube([20, light_hole, THICK_WALL])) # add two cable ties; # corner # base += translate([LENGTH_TOP - 18, WIDTH_TOP - 10, THICK_WALL]) # (cable_fasten(TIE_HEIGHT, TIE_WIDTH, THICK_WALL, True)) # laserbase base += translate([75 - 10, WIDTH_TOP - 20, THICK_WALL])(cable_fasten(TIE_HEIGHT, TIE_WIDTH, THICK_WALL, False)) # add fasteners at corners # bottom left and upper right corner upshift = THICK_WALL + height - HEIGHT_TOP + THICK_WALL base += translate([SCREW_FIXOFFST, SCREW_FIXOFFST, upshift])(threadedinsert(True, THICK_WALL, 4.0, 5.8)) base += translate( [LENGTH_TOP - SCREW_FIXOFFST, WIDTH_TOP - SCREW_FIXOFFST, upshift])(threadedinsert(False, THICK_WALL, 4.0, 5.8)) base += translate([LENGTH_TOP - SCREW_FIXOFFST, SCREW_FIXOFFST, upshift])(threadedinsert(True, THICK_WALL, 4.0, 5.8)) base += translate([SCREW_FIXOFFST, WIDTH_TOP - SCREW_FIXOFFST, upshift])(threadedinsert(False, THICK_WALL, 4.0, 5.8)) base = mirror([0, 1, 0])(base) return base