def groove_cuts(self): steps = 10 delta = 1.0 / steps return union()([ self.single_groove_cut(delta * (1 + index)) for index in range(steps) ])
def spaced_hole_punch(offsets, spacings, diameter, thickness): x_offset, y_offset, z_offset = offsets x_spacings, z_spacings = spacings hole = back(y_offset)(up(z_offset)(right(x_offset)(punch_hole( diameter, thickness)))) return union()( [up(z)(right(x)(hole)) for z in z_spacings for x in x_spacings])
def grid_plane(grid_unit=12, count=10, line_weight=0.1, plane='xz'): # Draws a grid of thin lines in the specified plane. Helpful for # reference during debugging. elle = count * grid_unit t = union() t.set_modifier('background') for i in range(-count // 2, count // 2 + 1): if 'xz' in plane: # xz-plane h = up(i * grid_unit)(cube([elle, line_weight, line_weight], center=True)) v = right(i * grid_unit)(cube([line_weight, line_weight, elle], center=True)) t.add([h, v]) # xy plane if 'xy' in plane: h = forward(i * grid_unit)(cube([elle, line_weight, line_weight], center=True)) v = right(i * grid_unit)(cube([line_weight, elle, line_weight], center=True)) t.add([h, v]) # yz plane if 'yz' in plane: h = up(i * grid_unit)(cube([line_weight, elle, line_weight], center=True)) v = forward(i * grid_unit)(cube([line_weight, line_weight, elle], center=True)) t.add([h, v]) return t
def ref_arrow_3d(arr_length, origin, label, ref_rotation=(0, 0, 0)): """ Create 3-axis ref frame, with color for x, y, z :param arr_length: length of arrow :param origin: set origin of arrows. (x, y, z) :param ref_rotation: tuple for rotation :param label: dictionary of labels for the three axis. {'x': label_x, 'y': label_y, 'z': label_z} :return: """ ref_frame = union() ref_frame.add( color([1, 0, 0])(arrow(heigth=arr_length, tail=origin, rotation=(0, 90, 0), label=label['x']))) # x_axis ref_frame.add( color([0, 1, 0])(arrow(heigth=arr_length, tail=origin, rotation=(-90, 0, 0), label=label['y']))) # y_axis ref_frame.add( color([0, 0, 1])(arrow(heigth=arr_length, tail=origin, rotation=(0, 0, 0), label=label['z']))) # z_axis ref_frame = rotate(list(ref_rotation))(ref_frame) return ref_frame
def flat(): body = square(size=[length, height]) holes = [] num_cutouts = int(ceil(length / step)) for i in xrange(num_cutouts + 1): holes.append(right((i + 1) * step)(slit())) body -= union()(holes) return body
def telescope_camera_event(event): """ Plot telescope + camera + event on it (if needed). Loop over all the telescopes with data in an event. The function create: - camera display with event visualization and arrows - telescope frame - position every telescope on its right position on ground :param event: event selected from simtel file :return: return the array to be rendered """ itel = list(event.r0.tels_with_data) print("id_telescopes:", itel) subinfo = event.inst.subarray sub_arr_trig = event.inst.subarray.select_subarray('sub_trig', itel).to_table() x_tel_trig = sub_arr_trig['tel_pos_x'].to('cm').value y_tel_trig = sub_arr_trig['tel_pos_y'].to('cm').value z_tel_trig = sub_arr_trig['tel_pos_z'].to('cm').value label_tel = sub_arr_trig['tel_id'] tel_names = sub_arr_trig['tel_description'] point_dir = { 'alt': event.mcheader.run_array_direction[1].to('deg'), 'az': event.mcheader.run_array_direction[0].to('deg') } array = union() itel = [3] sub_arr_trig.add_index('tel_id') for tel_id in itel: index = sub_arr_trig.loc_indices[tel_id] if subinfo.tel[tel_id].camera.cam_id in ['FlashCam']: continue print('-------------------------') print("tel_id processed: ", tel_id) tel_name = tel_names[index] camera_display = draw_camera(event=event, itel=tel_id, subarray=subinfo, scale_cam=1.6, tail_cut_bool=True) #, ref_axis=True) origin = (x_tel_trig[index], y_tel_trig[index], z_tel_trig[index]) tel_struct = telescope(tel_description=tel_name, camera_display_bool=camera_display, pointing=point_dir, origin=origin, tel_num=label_tel[index], ref_camera=True, ref_tel=False, sim_to_real=True) array.add(tel_struct) array.add(grid_plane()) return array
def assembly(): print "adapter needs height 32mm and we have %smm" % (8 * material_height) print "phone needs height 32mm and we have %smm" % (6 * material_height) print "total height %smm" % (len(ls) * material_height) layers = [] for i, l in enumerate(ls): l_inst = l() layer = linear_extrude(height=material_height)(l_inst.body) layer = up(i * (material_height + layer_z_gap))(layer) layer = color(l_inst.color)(layer) layers.append(layer) return union()(layers)
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 flat(): def offset(i, break_after=5): x = 0 if i < break_after else phone_width + layer_y_gap + radius y = i * (radius + layer_y_gap) y = y - break_after * (radius + layer_y_gap) if x > 0 else y return x, y layers = [] for i, l in enumerate(ls): x, y = offset(i) l_inst = l() layer = l_inst.body layer = color(l_inst.color)(layer) layers.append(right(x)(forward(y)(layer))) return union()(layers)
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 long_plank(self, width, plank_count): board = grounded_cube([width, self.table_length, self.plank_size]) groove = up(self.plank_size - self.plank_groove)( rotate([0, -45, 0])( right(self.plank_size / 2)( grounded_cube([self.plank_size, 2 * self.table_length, self.plank_size]) ) ) ) groove_offsets = [ width / (plank_count) * (index - (plank_count - 2) / 2) for index in range(plank_count - 1) ] grooves = union()([ right(groove_offset)(groove) for groove_offset in groove_offsets ]) return board - grooves
def main(filename): """ Main function to - load and calibrate the event from simtel file - pass the selected event to create array: - camera + event - telescope structure - tel id (w/o data_after_cleaning: Red (Y) or Green (N)) - add MC cross on ground - add ground ref frame :return: the full array plus the MC cross on ground and reference frame + grid for xy-plane """ array = union() event = load_calibrate(filename) # array.add(telescope_camera_event(event=event)) array.add(tilted_grid(event=event, tel_pos=False, zen_az_arrows=True)) array.add(ground_grid(event=event, tel_pos=False)) array = array + mc_details(event=event) file_out = 'ground.scad' scad_render_to_file(array, file_out)
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 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 ref_arrow_2d(length, origin, label, ref_rotation=(0, 0), inverted=False): """ Create 3-axis ref frame, with color for x,y :param length: length of arrow :param label. dictionary of labels for the 2 axis: {'x': label_x, 'y': label_y} :param origin: set origin of arrows. (x, y) :param ref_rotation: tuple for rotation :return: arrow with right text rotation. """ ref_frame = union() if inverted: ref_frame.add( color([1, 0, 0])(arrow(heigth=length, tail=origin, label=label['x'], rotation=(-90, 180, 0)))) # x_axis ref_frame.add( color([0, 1, 0])(multmatrix(m=rotation(90, 'x'))(arrow( heigth=length, tail=origin, label=label['y'], rotation=(0, 90, 0))))) # y_axis ref_frame = rotate(list(ref_rotation))(ref_frame) else: ref_frame.add( color([1, 0, 0])(multmatrix(m=rotation(-90, 'x'))(arrow( heigth=length, tail=origin, label=label['x'], rotation=(0, 90, 0))))) # x_axis ref_frame.add( color([0, 1, 0])(arrow(heigth=length, tail=origin, label=label['y'], rotation=(-90, 0, 0)))) # y_axis ref_frame = rotate(list(ref_rotation))(ref_frame) return ref_frame
def main(filename): """ Main function to - load and calibrate the event from simtel file - pass the selected event to create array: - camera + event - telescope structure - tel id (w/o data_after_cleaning: Red (Y) or Green (N)) - add MC cross on ground - add ground ref frame :return: the full array plus the MC cross on ground and reference frame + grid for xy-plane """ array = union() event = load_calibrate(filename) array.add(telescope_camera_event(event=event)) array = array + mc_details(event=event) # dimension, origin and label of reference arrow ref_arr = ref_arrow_3d(2000, origin=(1000, 1000, 0), label={ 'x': "x_gnd = NORTH", 'y': "y_gnd = WEST", 'z': "z_gnd" }) array = array + ref_arr if "Paranal" in filename: site = "Paranal" elif "palma" in filename: site = "LaPalma" else: site = "nosite" file_out = 'basic_geometry_4LST_' + site + '.scad' scad_render_to_file(array, file_out)
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 slit(): return rotate(a=back_bend_degrees)( forward(y_offset)( union()(hull()( circle(r=radius) + forward(height)(circle(r=radius))))))
def grid_pegs(single_peg, grid): return union()([left(x)(forward(y)(single_peg)) for (y, x) in grid])
def leg_frame(self): return union()([ self.single_leg(-self.leg_angle * x, self.leg_offset_x * x, self.frame_offset_y * y) for x in [-1, 1] for y in [-1, 1] ])
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 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