diff --git a/.gitignore b/.gitignore index c18dd8d..bee8a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -__pycache__/ +__pycache__ diff --git a/calculator.py b/calculator.py index 7d7dc79..d237fc0 100644 --- a/calculator.py +++ b/calculator.py @@ -5,6 +5,7 @@ unit_x = np.array([1, 0, 0]) unit_y = np.array([0, 1, 0]) unit_z = np.array([0, 0, 1]) + def get_axis(axis): "Axis are numbered from 1 to 3 from x to z." match axis: @@ -18,14 +19,16 @@ def get_axis(axis): ax = unit_x return ax -def proj(vec, axis: int =1): - """Simple vector projection onto an axis.""" + +def proj(vec, axis: int = 1): + """Simple vector projection onto an axis.""" ax = get_axis(axis) return np.dot(vec, ax) * ax + def rotate(v, angle=90, axis=1): "Rotate a vector with an angle around a axis with the right hand rule." - angle = angle/180 * np.pi + angle = angle / 180 * np.pi k = get_axis(axis) return ( @@ -33,15 +36,21 @@ def rotate(v, angle=90, axis=1): + np.cross(k, v) * np.sin(angle) + k * np.dot(k, v) * (1 - np.cos(angle)) ) - + + def agl(a, b): "Get the angle between two vectors. This is always between 0 and 180 degree." - return np.round(np.acos(np.dot(a, b)/(np.linalg.norm(a) * np.linalg.norm(b)))/(2 * np.pi) * 360) + return np.round( + np.acos(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))) + / (2 * np.pi) + * 360 + ) + def get_angles(source, target): """Main function to get the phi and theta angles for a source and a target vector. Both vectors must lie on the front half sphere. - Phi is from 0 to 180 where 0 means left when you look at the mirrors. The hardware is bounded between 45 and 135 degree. Thus the here provided angle needs to be subtracted by 45 and then doubled. - Theta is from 0 to 90 where 0 means up.""" + Phi is from 0 to 180 where 0 means left when you look at the mirrors. The hardware is bounded between 45 and 135 degree. Thus the here provided angle needs to be subtracted by 45 and then doubled. + Theta is from 0 to 90 where 0 means up.""" source_planar = source - proj(source, 3) target_planar = target - proj(target, 3) @@ -59,15 +68,15 @@ def get_angles(source, target): if source_phi < target_phi: rota = rotate(source_planar, phi_diff, 3) theta_diff = agl(rota, target) - phi = source_phi + phi_diff/2 + phi = source_phi + phi_diff / 2 else: rota = rotate(target_planar, phi_diff, 3) theta_diff = agl(rota, source) - phi = target_phi + phi_diff/2 + phi = target_phi + phi_diff / 2 if source_theta < target_theta: - theta = target_theta + theta_diff/2 + theta = target_theta + theta_diff / 2 else: - theta = source_theta + theta_diff/2 + theta = source_theta + theta_diff / 2 return (phi, theta) diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..36d6eef --- /dev/null +++ b/helpers.py @@ -0,0 +1,9 @@ +from objects.world import World + + +def print_status(world: World): + for i, mirror in enumerate(world.mirrors): + phi, theta = mirror.get_angles() + print( + f"Mirror {i} ({mirror.cluster_x}, {mirror.cluster_y}) angles -> phi: {phi:.2f}°, theta: {theta:.2f}°" + ) diff --git a/objects/mirror.py b/objects/mirror.py index 612a2b0..264a8fc 100644 --- a/objects/mirror.py +++ b/objects/mirror.py @@ -1,3 +1,8 @@ +""" +When theta motor is at 0 then mirror is up and when at 180 then mirror is front. +Phi 0 equals right and phi 180 equals left by 45 degrees. +""" + import numpy as np from objects.generic import Source, Target @@ -5,9 +10,10 @@ from objects.motor import Motor from calculator import get_angles + class Mirror: def __init__(self, world, cluster_x=0, cluster_y=0): - self.world = world + self.world = world # TODO: Fix this cyclic reference self.cluster_x = cluster_x self.cluster_y = cluster_y @@ -32,7 +38,7 @@ class Mirror: rel_source = source.pos - rot_pos rel_target = target.pos - rot_pos - phi, theta = get_angles(rel_source, rel_target) # ty:ignore[unresolved-reference] + phi, theta = get_angles(rel_source, rel_target) # Update the angles based on the normals in rotated positions self.motor_phi.set_angle(phi) diff --git a/objects/world.py b/objects/world.py index 6510117..2689c4f 100644 --- a/objects/world.py +++ b/objects/world.py @@ -53,3 +53,4 @@ class World: y_rot = y z_rot = -x * sin_t + z * cos_t return np.array([x_rot, y_rot, z_rot]) + diff --git a/simulation.py b/simulation.py index 5cd6a40..6338dc0 100644 --- a/simulation.py +++ b/simulation.py @@ -1,3 +1,4 @@ +from helpers import print_status import time from objects.generic import Source, Target @@ -6,42 +7,21 @@ from objects.mirror import Mirror from objects.board import Board # Solar module for simulation of world -STEP = 10 LOOP_DELAY = 0.005 # In seconds # Testing embedding the mirrors in the world board = Board() world = World(board, tilt_deg=0) -HEIGHT = 30 - source = Source(world, pos=(0, 50, 0)) target = Target(world, pos=(0, 50, 0)) -# Create mirrors in a 3x2 grid +# Create mirrors in a grid for x in range(2): for y in range(1): mirror = Mirror(world, cluster_x=x, cluster_y=y) world.add_mirror(mirror) -world.update_mirrors_from_source_target(source, target) - -def print_status(): - for i, mirror in enumerate(world.mirrors): - phi, theta = mirror.get_angles() - print(f"Mirror {i} ({mirror.cluster_x}, {mirror.cluster_y}) angles -> phi: {phi:.2f}°, theta: {theta:.2f}°") - - -a = 1 -t = time.time() - -world.mirrors[0].motor_theta.set_angle(180) -world.mirrors[0].motor_phi.set_angle(180) -world.mirrors[1].motor_phi.set_angle(0) -world.mirrors[1].motor_theta.set_angle(0) - -print_status() - # Main try: while True: @@ -51,11 +31,9 @@ try: #print(target.pos) world.update_mirrors_from_source_target(source, target) - print_status() + print_status(world) time.sleep(LOOP_DELAY) - t = time.time() - except KeyboardInterrupt: pass diff --git a/uv.lock b/uv.lock index 49165e1..a3f7282 100644 --- a/uv.lock +++ b/uv.lock @@ -516,7 +516,7 @@ wheels = [ [[package]] name = "solarmotor" -version = "0.1.0" +version = "0.1.1" source = { virtual = "." } dependencies = [ { name = "adafruit-blinka" },