|
1 | 1 | from dataclasses import dataclass |
2 | 2 | import numpy as np |
| 3 | +from scipy.spatial.transform import Rotation |
3 | 4 |
|
4 | 5 | import brahe |
5 | 6 | from brahe.epoch import Epoch |
@@ -59,16 +60,82 @@ class SolarGeneration: |
59 | 60 | SOLAR_FLUX = 1373 # The solar power flux at the Earth's orbital radius in W/m^2. |
60 | 61 | PANEL_EFFICIENCY = 0.15 # The efficiency of the solar panels, as a fraction in [0, 1]. |
61 | 62 |
|
62 | | - def __init__(self, config: dict) -> None: |
63 | | - self.surfaces = SolarGeneration.parse_solar_config(config["satellite"]["solar"]) |
| 63 | + def __init__(self, deployables_dir: np.ndarray, deployables_tilt_angle: float) -> None: |
| 64 | + self.surfaces = SolarGeneration.get_solar_config(deployables_dir, deployables_tilt_angle) |
64 | 65 | self.sample_resolution = 0.05 # The resolution of the occlusion sampling grid, in meters. |
65 | 66 |
|
66 | 67 | @staticmethod |
67 | | - def parse_solar_config(surfaces: list[dict[str, bool | list[float] | float]]) -> list[Surface]: |
68 | | - def to_np(surface: dict[str, bool | list[float] | float]) -> dict[str, bool | np.ndarray | float]: |
69 | | - return {key: np.array(value) if isinstance(value, list) else value |
70 | | - for key, value in surface.items()} |
71 | | - return [Surface(**to_np(surface)) for surface in surfaces] |
| 68 | + def get_solar_config(deployables_dir: np.ndarray, deployables_tilt_angle: float) -> list[Surface]: |
| 69 | + """ |
| 70 | + Returns a list of Surface objects representing the solar panels on the satellite. |
| 71 | +
|
| 72 | + :param deployables_dir: A 3-element numpy array representing the direction of the deployable solar panels. |
| 73 | + Must be a unit vector with exactly one non-zero element. |
| 74 | + :param deployables_tilt_angle: The angle in radians by which the deployable solar panels are tilted. |
| 75 | + See the diagram below for the definition of the tilt angle. |
| 76 | + :return: A list of Surface objects representing the configuration of the satellite. |
| 77 | + """ |
| 78 | + assert deployables_dir.shape == (3,), "deployables_dir must be a 3-element numpy array." |
| 79 | + assert np.sum(np.abs(deployables_dir) == 1) == 1 and np.sum(deployables_dir == 0) == 2, \ |
| 80 | + "deployables_dir must be a unit vector with exactly one non-zero element." |
| 81 | + |
| 82 | + R = 0.05 # Half the side length of the cubesat in meters |
| 83 | + |
| 84 | + cube_surfaces = [Surface(is_solar_panel=True, |
| 85 | + center=np.array([R, 0, 0]), |
| 86 | + normal=np.array([1, 0, 0]), |
| 87 | + x_dir=np.array([0, 1, 0]), |
| 88 | + y_dir=np.array([0, 0, 1]), |
| 89 | + width=0.1, |
| 90 | + height=0.1)] # +x face |
| 91 | + cube_surfaces.append(cube_surfaces[0].transform(np.array([[0, 1, 0], |
| 92 | + [1, 0, 0], |
| 93 | + [0, 0, 1]]))) # +y face |
| 94 | + cube_surfaces.append(cube_surfaces[0].transform(np.array([[0, 0, 1], |
| 95 | + [0, 1, 0], |
| 96 | + [1, 0, 0]]))) # +z face |
| 97 | + cube_surfaces.append(cube_surfaces[0].transform(np.array([[-1, 0, 0], |
| 98 | + [0, 1, 0], |
| 99 | + [0, 0, 1]]))) # -x face |
| 100 | + cube_surfaces.append(cube_surfaces[1].transform(np.array([[1, 0, 0], |
| 101 | + [0, -1, 0], |
| 102 | + [0, 0, 1]]))) # -y face |
| 103 | + cube_surfaces.append(cube_surfaces[2].transform(np.array([[1, 0, 0], |
| 104 | + [0, 1, 0], |
| 105 | + [0, 0, -1]]))) # -z face |
| 106 | + |
| 107 | + """ |
| 108 | + deployables_tilt_angle |
| 109 | + | / deployables_dir |
| 110 | + | / ^ |
| 111 | + ________|/ | |
| 112 | + | | | |
| 113 | + | | --> deployables_offset_dir |
| 114 | + |_______| |
| 115 | + deployables_tilt_dir is into the page |
| 116 | + """ |
| 117 | + deployables_tilt_dir = np.roll(deployables_dir, 1) # doesn't matter which of the 4 perpendiculars we choose |
| 118 | + deployables_offset_dir = np.cross(deployables_tilt_dir, deployables_dir) |
| 119 | + deployables_tilt = Rotation.from_rotvec(deployables_tilt_angle * deployables_tilt_dir).as_matrix() |
| 120 | + |
| 121 | + deployable_surfaces = [Surface(is_solar_panel=True, |
| 122 | + center=(np.eye(3) + deployables_tilt) * R * deployables_dir + R * deployables_offset_dir, |
| 123 | + normal=deployables_tilt @ deployables_offset_dir, |
| 124 | + x_dir=deployables_tilt_dir, |
| 125 | + y_dir=deployables_tilt @ deployables_dir, |
| 126 | + width=0.1, |
| 127 | + height=0.1)] |
| 128 | + |
| 129 | + # add the 3 other rotated copies of the deployable surfaces |
| 130 | + rot_90 = Rotation.from_rotvec((np.pi / 2) * deployables_dir).as_matrix() |
| 131 | + for i in range(0, 4): |
| 132 | + deployable_surfaces.append(deployable_surfaces[0].transform(rot_90 ** i)) |
| 133 | + |
| 134 | + # add deployable surfaces with flipped normals |
| 135 | + for deployable_surface in deployable_surfaces: |
| 136 | + deployable_surfaces.append(deployable_surface.flip_normal()) |
| 137 | + |
| 138 | + return cube_surfaces + deployable_surfaces |
72 | 139 |
|
73 | 140 | @staticmethod |
74 | 141 | def get_intersections(surface: Surface, ray_starts: np.ndarray, ray_dir: np.ndarray) -> np.ndarray: |
|
0 commit comments