def test_move(monkeypatch, freebody): freebody.mass = 2 freebody.pitch_rot_inertia = 3 freebody.rot_velocity = 2 freebody.angle = 1 freebody.velocity = Vector2(12, 3) freebody.position = Vector2(5, 1) def force_and_moment_at_cg_mock(): return Vector2(50, 10), 1.5 freebody.force_and_moment_at_cg = force_and_moment_at_cg_mock total_force, moment_z = freebody.move(0.1) npt.assert_almost_equal(freebody.rot_velocity, 2.05) npt.assert_almost_equal(freebody.angle, 1.205) npt.assert_almost_equal(freebody.velocity.x, 14.5) npt.assert_almost_equal(freebody.velocity.y, 3.5) npt.assert_almost_equal(freebody.position.x, 6.45) npt.assert_almost_equal(freebody.position.y, 1.35) npt.assert_almost_equal(total_force.x, 50) npt.assert_almost_equal(total_force.y, 10) npt.assert_almost_equal(moment_z, 1.5)
def test_component_vector_in_absolute_frame(mocker): component = mocker.Mock() component.position = Vector2(1.4, 0.6) component.angle = math.radians(24) vector = Vector2(-0.4, 0.1) new_vector = component_vector_in_absolute_frame(vector, component) npt.assert_almost_equal(new_vector.x, 0.994, decimal=3) npt.assert_almost_equal(new_vector.y, 0.53, decimal=3)
class FreeBody(BaseComponent): position_cg: Vector2 = None pitch_rot_inertia: float = None ambient: Ambient = None # State attributes position: Vector2 = Vector2(0, 0) angle: float = 0 velocity: Vector2 = Vector2(0.00001, 0.00001) rot_velocity: float = 0 def __attrs_post_init__(self): self.add_external_force_function('weight', self.get_total_weight) def reset_state(self): self.position = Vector2(0, 0) self.angle = 0 self.velocity = Vector2(0.00001, 0.00001) self.rot_velocity = 0 super().reset_state() @property def gravitational_center(self) -> Vector2: return self.position_cg def force_and_moment_at_cg(self): force_at_cg = Vector2(0, 0) moment_at_cg = 0 force_at_origin, moment_at_origin = self.force_and_moment_at_component_origin( ) force_at_cg += force_at_origin moment_at_cg += moment_at_origin moment_at_cg += (Vector2(0, 0) - self.gravitational_center).cross(force_at_origin) return force_at_cg, moment_at_cg def get_total_weight(self): weight_mag = gravitational_acceleration * self.total_mass weight_angle = math.radians(-90) - self.angle weight = Vector2(r=weight_mag, theta=weight_angle) return weight, self.gravitational_center def move(self, time_step): total_force, moment_z = self.force_and_moment_at_cg() acc = total_force / self.total_mass ang_acc_z = moment_z / self.pitch_rot_inertia self.rot_velocity = self.rot_velocity + ang_acc_z * time_step self.angle = self.angle + self.rot_velocity * time_step self.velocity = self.velocity + acc * time_step self.position = self.position + self.velocity * time_step return total_force, moment_z
def test_states(freebody): freebody.position = Vector2(15.1, 1.2) freebody.angle = math.radians(15) freebody.velocity = Vector2(12.1, 0.7) freebody.rot_velocity = 14 assert (freebody.position == Vector2(15.1, 1.2)) assert (freebody.angle == math.radians(15)) assert (freebody.velocity == Vector2(12.1, 0.7)) assert (freebody.rot_velocity == 14)
def force_and_moment_at_cg(self): force_at_cg = Vector2(0, 0) moment_at_cg = 0 force_at_origin, moment_at_origin = self.force_and_moment_at_component_origin( ) force_at_cg += force_at_origin moment_at_cg += moment_at_origin moment_at_cg += (Vector2(0, 0) - self.gravitational_center).cross(force_at_origin) return force_at_cg, moment_at_cg
def test_reset_state(freebody): freebody.position = Vector2(2, 4) freebody.angle = 3 freebody.velocity = Vector2(7, 2) freebody.rot_velocity = 6 freebody.reset_state() assert (freebody.position == Vector2(0, 0)) assert (freebody.angle == 0) assert (freebody.velocity == Vector2(0.00001, 0.00001)) assert (freebody.rot_velocity == 0)
def test_component_vector_coords_in_absolute_frame(mocker): component = mocker.Mock() component.position = Vector2(1.4, 0.6) component.angle = math.radians(25) vector_origin = Vector2(-0.4, 0.1) vector = Vector2(0, 0.5) x0, y0, x, y = component_vector_coords_in_absolute_frame( vector_origin, vector, component) npt.assert_almost_equal(x0, 0.994, decimal=3) npt.assert_almost_equal(y0, 0.521, decimal=3) npt.assert_almost_equal(x, 0.784, decimal=3) npt.assert_almost_equal(y, 0.975, decimal=3)
def test_gear_reaction(plane, main_landing_gear): main_landing_gear.set_parent(plane) plane.velocity = Vector2(6, 0.4) # Plane on air (position.y = 2m), so no reaction on landing gear is expected plane.position = Vector2(10, 2) reaction, contact_point = main_landing_gear.gear_reaction() assert(type(contact_point) is Vector2) npt.assert_almost_equal(reaction.y, 0) # Plane on ground (position.y = 0m), so reaction on landing gear is expected plane.position = Vector2(10, 0) reaction, contact_point = main_landing_gear.gear_reaction() npt.assert_almost_equal(reaction.y, 80.0)
def test_angle_of_attack(freebody_component, attached_component): freebody_component.velocity = Vector2(r=12, theta=math.radians(5)) attached_component.set_parent(freebody_component) npt.testing.assert_almost_equal( math.degrees(freebody_component.angle_of_attack), -5.0, decimal=1) npt.testing.assert_almost_equal( math.degrees(attached_component.angle_of_attack), 4.0, decimal=1)
def force_and_moment_from_external_forces(self): force = Vector2(0.00001, 0) moment = 0.0 for force_function in self.external_forces.values(): _force, _application_point = force_function() force += _force moment += _application_point.cross(_force) return force, moment
def test_translate(): v1 = Vector2(-1.4, 0.6) v1_trans1 = translate(v1, 0.3, 0.6) v1_trans2 = translate(v1, -0.3, -0.6) npt.assert_almost_equal(v1_trans1.x, -1.1) npt.assert_almost_equal(v1_trans1.y, 1.2) npt.assert_almost_equal(v1_trans2.x, -1.7) npt.assert_almost_equal(v1_trans2.y, 0.0)
def attached_component(): attached_component = AttachedComponent(name='attached_component', type='generic_attached_component', mass=1.4, relative_position=Vector2( -0.4, 0.1), relative_angle=math.radians(9)) return attached_component
def test_transform(): v1 = Vector2(0.7, 0.1) v1_trans1 = transform(v1, math.radians(90), -0.2, 0.5) v1_trans2 = transform(v1, math.radians(-90), -0.2, 0.5) npt.assert_almost_equal(v1_trans1.x, -0.3) npt.assert_almost_equal(v1_trans1.y, 1.2) npt.assert_almost_equal(v1_trans2.x, -0.1) npt.assert_almost_equal(v1_trans2.y, -0.2)
def payload(): payload = AttachedComponent( name='payload_generic', type='payload', mass=3.0, relative_position=Vector2(-0.6, 0.08), relative_angle=math.radians(9) ) return payload
def freebody_component(): freebody_component = FreeBody(name='component', type='generic_component', mass=3.4, angle=math.radians(5), position_cg=Vector2(-0.7, 0.2), pitch_rot_inertia=30.0, ambient=Ambient()) return freebody_component
def gear_reaction(self): displacement = self.height - self.position.y axial_velocity = self.velocity.y if displacement > 0: reaction_mag = self.spring_coeff * displacement - self.dump_coeff * axial_velocity else: reaction_mag = 0 reaction = Vector2(0, reaction_mag) return reaction, self.floor_contact_point
def get_thrust(self): angle_of_attack = self.angle_of_attack axial_velocity = self.velocity.rotated(angle_of_attack).x thrust_mag = get_axial_thrust_from_linear_model( self.ambient.air_density, axial_velocity, self.static_thrust, self.linear_coefficient) thrust = Vector2(thrust_mag, 0) return thrust, self.thrust_center
def get_drag(self): if self.inverted: CD = get_CD_inv(self.angle_of_attack) else: CD = get_CD(self.angle_of_attack) drag_mag = get_drag(self.ambient.air_density, self.velocity.r, self.area, CD) drag_angle = math.radians(180) - self.angle_of_attack drag = Vector2(r=drag_mag, theta=drag_angle) return drag, self.aerodynamic_center
def get_lift(self): if self.inverted: CL = get_CL_inv(self.angle_of_attack) else: CL = get_CL(self.angle_of_attack) lift_mag = get_lift(self.ambient.air_density, self.velocity.r, self.area, CL) lift_angle = math.radians(90) - self.angle_of_attack lift = Vector2(r=lift_mag, theta=lift_angle) return lift, self.aerodynamic_center
def freebody(): env = Ambient() freebody = FreeBody( name='freebody', type='generic_freebody', mass=23.4, position_cg=Vector2(-0.2, 0.02), pitch_rot_inertia=5.2, ambient=env, ) return freebody
def plane(): env = Ambient() plane = FreeBody( name='plane', type='plane', mass=23.4, position_cg=Vector2(-0.2, 0.02), pitch_rot_inertia=5.2, ambient=env, ) return plane
def force_and_moment_from_children(self): force = Vector2(0, 0) moment = 0.0 for component in self.children.values(): _force, _moment = component.force_and_moment_at_component_origin() force += _force.rotated(component.relative_angle) moment += _moment moment += component.relative_position.cross( _force.rotated(component.relative_angle)) return force, moment
def test_gear_friction(plane, main_landing_gear): main_landing_gear.set_parent(plane) plane.velocity = Vector2(6, 0.4) # Plane on air (position.y = 2m), so no friction on landing gear is expected plane.position = Vector2(10, 2) friction, contact_point = main_landing_gear.gear_friction() assert(type(contact_point) is Vector2) npt.assert_almost_equal(friction.x, 0) # Plane on ground (position.y = 0m), going forward, expected friction on negative x direction plane.position = Vector2(10, 0) friction, contact_point = main_landing_gear.gear_friction() npt.assert_almost_equal(friction.x, -4.0) # Plane on ground (position.y = 0m), going backwards, expected friction on positive x direction plane.velocity = Vector2(-6, 0.4) plane.position = Vector2(10, 0) friction, contact_point = main_landing_gear.gear_friction() npt.assert_almost_equal(friction.x, 4.0)
def gear_friction(self): normal_force, contact_point = self.gear_reaction() if self.velocity.x > 0: velocity_direction = 1 else: velocity_direction = -1 friction_mag = self.friction_coeff * normal_force.r * velocity_direction friction = Vector2(-friction_mag, 0) return friction, self.floor_contact_point
def main_landing_gear(): main_landing_gear = LandingGear( name='main_landing_gear', relative_position=Vector2(x=-0.2, y=0), relative_angle=math.radians(0), mass=0.3, height=0.1, spring_coeff=1000, dump_coeff=50, friction_coeff=0.05 ) return main_landing_gear
def test_rotate(): v1 = Vector2(0.7, 0.1) v1_rot_90 = rotate(v1, math.radians(90)) v1_rot_n90 = rotate(v1, math.radians(-90)) v1_rot_n135 = rotate(v1, math.radians(-135)) npt.assert_almost_equal(v1_rot_90.x, -0.1) npt.assert_almost_equal(v1_rot_90.y, 0.7) npt.assert_almost_equal(v1_rot_n90.x, 0.1) npt.assert_almost_equal(v1_rot_n90.y, -0.7) npt.assert_almost_equal(v1_rot_n135.x, -0.424, decimal=3) npt.assert_almost_equal(v1_rot_n135.y, -0.565, decimal=3) v2 = Vector2(-1.4, 0.6) v2_rot_90 = rotate(v2, math.radians(90)) v2_rot_n90 = rotate(v2, math.radians(-90)) v2_rot_n135 = rotate(v2, math.radians(-135)) npt.assert_almost_equal(v2_rot_90.x, -0.6) npt.assert_almost_equal(v2_rot_90.y, -1.4) npt.assert_almost_equal(v2_rot_n90.x, 0.6) npt.assert_almost_equal(v2_rot_n90.y, 1.4) npt.assert_almost_equal(v2_rot_n135.x, 1.414, decimal=3) npt.assert_almost_equal(v2_rot_n135.y, 0.565, decimal=3)
def test_velocity(attached_component, freebody_component): attached_component.set_parent(freebody_component) freebody_component.velocity = Vector2(20, 10) assert (attached_component.velocity == Vector2(20, 10)) freebody_component.velocity = Vector2(-5, 0) assert (attached_component.velocity == Vector2(-5, 0)) freebody_component.velocity = Vector2(-15, -20) assert (attached_component.velocity == Vector2(-15, -20))
def force_and_moment_at_component_origin(self): force = Vector2(0, 0) moment = 0.0 _force, _moment = self.force_and_moment_from_external_forces() force += _force moment += _moment _moment = self.moment_from_external_moments() moment += _moment _force, _moment = self.force_and_moment_from_children() force += _force moment += _moment return force, moment
def test_position(freebody_component, attached_component): attached_component.set_parent(freebody_component) freebody_component.angle = math.radians(0) freebody_component.position = Vector2(0, 0) assert (attached_component.position == Vector2(-0.4, 0.1)) freebody_component.angle = math.radians(2) freebody_component.position = Vector2(-0.5, 0.2) assert (attached_component.position == Vector2(-0.9032462804778885, 0.2859792840209092)) freebody_component.angle = math.radians(-2) freebody_component.position = Vector2(0.3, -0.5) assert (attached_component.position == Vector2(-0.09626638113738828, -0.38610111861709))
def aerodynamic_center(self): return Vector2(-0.25 * self.mean_aerodynamic_chord, 0)