diff --git a/.vscode/settings.json b/.vscode/settings.json index 59517ba..acb0b73 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,17 @@ "--no-pretty", "--disallow-untyped-calls", "--disallow-untyped-defs" + ], + "cSpell.words": [ + "anglecalculations", + "coefficientsapprox", + "coordconversion", + "dtype", + "Fluctuationfield", + "ndarray", + "sailsim", + "Squallfield", + "timestep", + "Windfield" ] } diff --git a/requirements.txt b/requirements.txt index 91c0c7f..e02564e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ PySide6 +numpy opensimplex >= 0.4 diff --git a/requirements_dev.txt b/requirements_dev.txt index f335c72..19e419e 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -8,5 +8,6 @@ mypy pycodestyle pydocstyle autopep8 +autoflake tox diff --git a/sailsim/boat/Boat.py b/sailsim/boat/Boat.py index cd87780..50dc87f 100644 --- a/sailsim/boat/Boat.py +++ b/sailsim/boat/Boat.py @@ -1,9 +1,13 @@ -from math import sqrt, pi, sin, cos +"""This file contains the boat class.""" + +from numpy import sqrt, pi, sin, cos, array, ndarray, zeros +from numpy.linalg import norm from sailsim.boat.FrameList import FrameList from sailsim.sailor.Sailor import Sailor -from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval +from sailsim.utils.anglecalculations import angleKeepInterval from sailsim.utils.coordconversion import cartToRadiusSq, cartToArg +from sailsim.utils import Wrench from sailsim.boat.coefficientsapprox import coefficientAirDrag, coefficientAirLift, coefficientWaterDrag, coefficientWaterLift @@ -22,45 +26,30 @@ class Boat: temp_leewayAngle: float temp_angleOfAttack: float - temp_forceX: float - temp_forceY: float - temp_sailDragX: float - temp_sailDragY: float - temp_sailLiftX: float - temp_sailLiftY: float - temp_centerboardDragX: float - temp_centerboardDragY: float - temp_centerboardLiftX: float - temp_centerboardLiftY: float - temp_rudderDragX: float - temp_rudderDragY: float - temp_rudderLiftX: float - temp_rudderLiftY: float - - temp_torque: float - temp_waterDragTorque: float - temp_rudderTorque: float - temp_centerboardTorque: float + temp_wrench: Wrench + temp_sailDrag: Wrench + temp_sailLift: Wrench + temp_centerboardDrag: Wrench + temp_centerboardLift: Wrench + temp_rudderDrag: Wrench + temp_rudderLift: Wrench + temp_hullDrag: Wrench sailor: Sailor - def __init__(self, posX: float = 0, posY: float = 0, direction: float = 0, speedX: float = 0, speedY: float = 0, angSpeed: float = 0) -> None: + def __init__(self, pose: ndarray = zeros(3), speed: ndarray = zeros(3)) -> None: """ Create a boat. Args: - posX: x position of the boat (in m) - posY: y position of the boat (in m) - direction: direction the boat is pointing (in rad) - speedX: speed in x direction (in m/s) - speedY: speed in y direction (in m/s) - angSpeed: angular speed in z direction (in rad/s) + pose: position and orientation of the boat (in m or rad) (x, y, direction) + speed: speed (in m/s or rad/s) (x, y, angular) """ # Static properties self.length: float = 4.2 # m self.width: float = 1.63 # m self.mass: float = 80 # kg - self.momentumInertia: float = 1/12 * self.mass * (pow(self.length, 2) + pow(self.width, 2)) # kg/m^2 + self.momentumInertia: float = 1 / 12 * self.mass * (pow(self.length, 2) + pow(self.width, 2)) # kg/m^2 self.sailArea: float = 7.45 # m^2 self.hullArea: float = 4 # m^2 self.centerboardArea: float = 1 # m^2 # self.centerboardDepth * self.centerboardLength @@ -73,13 +62,9 @@ def __init__(self, posX: float = 0, posY: float = 0, direction: float = 0, speed self.rudderLever: float = 2.1 # m # Dynamic properties - self.posX: float = posX - self.posY: float = posY - self.speedX: float = speedX - self.speedY: float = speedY + self.pose: ndarray = pose + self.speed: ndarray = speed - self.direction: float = directionKeepInterval(direction) - self.angSpeed: float = angSpeed # rad/s self.pivot: float = 0.5 * self.length # m self.mainSailAngle: float = 0 @@ -99,38 +84,33 @@ def __init__(self, posX: float = 0, posY: float = 0, direction: float = 0, speed self.frameList: FrameList = FrameList() # Simulation methods - def applyCauses(self, forceX: float, forceY: float, torque: float, interval: float) -> None: + def applyCauses(self, wrench: Wrench, interval: float) -> None: """Change speed according a force & torque given.""" # Translation: △v = a * t; F = m * a -> △v = F / m * t # Rotation: △ω = α * t; M = I * α -> △ω = M / I * t - self.speedX += forceX / self.mass * interval - self.speedY += forceY / self.mass * interval - self.angSpeed += torque / self.momentumInertia * interval + self.speed = self.speed + wrench / array([self.mass, self.mass, self.momentumInertia], dtype=float) * interval def moveInterval(self, interval: float) -> None: """Change position according to sailsDirection and speed.""" # △s = v * t; △α = ω * t - self.posX += self.speedX * interval - self.posY += self.speedY * interval - self.direction = directionKeepInterval(self.direction + self.angSpeed * interval) + self.pose = self.pose + self.speed * interval def runSailor(self) -> None: """Activate the sailing algorithm to decide what the boat should do.""" if self.sailor is not None: # Run sailor if sailor exists self.sailor.run( - self.posX, - self.posY, + self.pose[0], + self.pose[1], self.temp_boatSpeed, - cartToArg(self.speedX, self.speedY), - self.direction, + cartToArg(self.speed[0], self.speed[1]), + self.pose[2], self.temp_apparentWindSpeed, self.temp_apparentWindAngle ) # Retrieve boat properties from Sailor self.mainSailAngle = self.sailor.mainSailAngle - # self.direction = self.sailor.boatDirection self.rudderAngle = self.sailor.rudderAngle def updateTemporaryData(self, trueWindX: float, trueWindY: float) -> None: @@ -145,7 +125,7 @@ def updateTemporaryData(self, trueWindX: float, trueWindY: float) -> None: self.temp_leewayAngle = self.calcLeewayAngle() self.temp_angleOfAttack = self.angleOfAttack(self.temp_apparentWindAngle) - def resultingCauses(self) -> tuple[float, float, float]: + def resultingCauses(self) -> Wrench: """Add up all acting forces and return them as a tuple.""" # calculate flowSpeed (flowSpeedRudderX, flowSpeedRudderY) = self.leverSpeedVector(self.rudderLever) @@ -156,64 +136,56 @@ def resultingCauses(self) -> tuple[float, float, float]: flowSpeedCenterboardSq = cartToRadiusSq(flowSpeedCenterboardX, flowSpeedCenterboardY) flowSpeedCenterboard = sqrt(flowSpeedCenterboardSq) - # normalise apparent wind vector and boat speed vector - (dirNormX, dirNormY) = (sin(self.direction), cos(self.direction)) - # if vector is (0, 0) set normalised vector to (0, 0) as well - (apparentWindNormX, apparentWindNormY) = (self.temp_apparentWindX / self.temp_apparentWindSpeed, self.temp_apparentWindY / self.temp_apparentWindSpeed) if not self.temp_apparentWindSpeed == 0 else (0, 0) # normalised apparent wind vector - # (speedNormX, speedNormY) = (self.speedX / self.temp_boatSpeed, self.speedY / self.temp_boatSpeed) if not self.temp_boatSpeed == 0 else (0, 0) # normalized speed vector - (flowSpeedRudderNormX, flowSpeedRudderNormY) = (flowSpeedRudderX / flowSpeedRudder, flowSpeedRudderY / flowSpeedRudder) if not flowSpeedRudder == 0 else (0, 0) # normalised speed vector - (flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) = (flowSpeedCenterboardX / flowSpeedCenterboard, flowSpeedCenterboardY / flowSpeedCenterboard) if not flowSpeedCenterboard == 0 else (0, 0) # normalised speed vector + # normalize apparent wind vector and boat speed vector + dirNorm = array([sin(self.pose[2]), cos(self.pose[2])]) + # if vector is (0, 0) set normalized vector to (0, 0) as well + apparentWindNorm: ndarray = array([self.temp_apparentWindX, self.temp_apparentWindY]) / self.temp_apparentWindSpeed if not self.temp_apparentWindSpeed == 0 else zeros(2) # normalized apparent wind vector + flowSpeedRudderNorm: ndarray = array([flowSpeedRudderX, flowSpeedRudderY]) / flowSpeedRudder if not flowSpeedRudder == 0 else zeros(2) # normalized speed vector + flowSpeedCenterboardNorm: ndarray = array([flowSpeedCenterboardX, flowSpeedCenterboardY]) / flowSpeedCenterboard if not flowSpeedCenterboard == 0 else zeros(2) # normalized speed vector # Sail forces - (self.temp_sailDragX, self.temp_sailDragY) = self.sailDrag(self.temp_apparentWindSpeed, apparentWindNormX, apparentWindNormY) - (self.temp_sailLiftX, self.temp_sailLiftY) = self.sailLift(self.temp_apparentWindSpeed, apparentWindNormX, apparentWindNormY) + self.temp_sailDrag = self.sailDrag(self.temp_apparentWindSpeed, apparentWindNorm) + self.temp_sailLift = self.sailLift(self.temp_apparentWindSpeed, apparentWindNorm) # Centerboard forces - (self.temp_centerboardDragX, self.temp_centerboardDragY) = self.centerboardDrag(flowSpeedCenterboardSq, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) - (self.temp_centerboardLiftX, self.temp_centerboardLiftY) = self.centerboardLift(flowSpeedCenterboardSq, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) + self.temp_centerboardDrag = self.centerboardDrag(flowSpeedCenterboardSq, flowSpeedCenterboardNorm, dirNorm) + self.temp_centerboardLift = self.centerboardLift(flowSpeedCenterboardSq, flowSpeedCenterboardNorm, dirNorm) # Rudder forces - (self.temp_rudderDragX, self.temp_rudderDragY) = self.rudderDrag(flowSpeedRudderSq, flowSpeedRudderNormX, flowSpeedRudderNormY) - (self.temp_rudderLiftX, self.temp_rudderLiftY) = self.rudderLift(flowSpeedRudderSq, flowSpeedRudderNormX, flowSpeedRudderNormY) + self.temp_rudderDrag = self.rudderDrag(flowSpeedRudderSq, flowSpeedRudderNorm, dirNorm) + self.temp_rudderLift = self.rudderLift(flowSpeedRudderSq, flowSpeedRudderNorm, dirNorm) # TODO Hull forces + self.temp_hullDrag = self.waterDrag() - # Torques - self.temp_waterDragTorque = self.waterDragTorque() - self.temp_centerboardTorque = self.centerboardTorque(self.temp_centerboardDragX + self.temp_centerboardLiftX, self.temp_centerboardDragY + self.temp_centerboardLiftY, dirNormX, dirNormY) - self.temp_rudderTorque = self.rudderTorque(self.temp_rudderDragX + self.temp_rudderLiftX, self.temp_rudderDragY + self.temp_rudderLiftY, dirNormX, dirNormY) - - self.temp_forceX = self.temp_sailDragX + self.temp_sailLiftX + self.temp_centerboardDragX + self.temp_centerboardLiftX + self.temp_rudderDragX + self.temp_rudderLiftX - self.temp_forceY = self.temp_sailDragY + self.temp_sailLiftY + self.temp_centerboardDragY + self.temp_centerboardLiftY + self.temp_rudderDragY + self.temp_rudderLiftY - self.temp_torque = self.temp_waterDragTorque + self.temp_centerboardTorque + self.temp_rudderTorque - - return (self.temp_forceX, self.temp_forceY, self.temp_torque) + self.temp_wrench = (self.temp_sailDrag + self.temp_sailLift + self.temp_centerboardDrag + self.temp_centerboardLift + self.temp_rudderDrag + self.temp_rudderLift + self.temp_hullDrag).view(Wrench) + return self.temp_wrench # Import force and torque functions - from sailsim.boat.boat_forces import leverSpeedVector, sailDrag, sailLift, centerboardDrag, centerboardLift, rudderDrag, rudderLift, scalarToDragForce, scalarToLiftForce - from sailsim.boat.boat_torques import waterDragTorque, centerboardTorque, rudderTorque + from sailsim.boat.boat_forces import leverSpeedVector, sailDrag, sailLift, centerboardDrag, centerboardLift, rudderDrag, rudderLift, waterDrag, scalarToLiftForce def boatSpeed(self) -> float: """Return speed of the boat.""" - return sqrt(pow(self.speedX, 2) + pow(self.speedY, 2)) + return float(norm(self.speed[:2])) # Angle calculations def calcLeewayAngle(self) -> float: """Calculate and return the leeway angle.""" - return angleKeepInterval(cartToArg(self.speedX, self.speedY) - self.direction) + return angleKeepInterval(cartToArg(self.speed[0], self.speed[1]) - self.pose[2]) def apparentWind(self, trueWindX: float, trueWindY: float) -> tuple[float, float]: """Return apparent wind by adding true wind and speed.""" - return (trueWindX - self.speedX, trueWindY - self.speedY) + return (trueWindX - self.speed[0], trueWindY - self.speed[1]) def apparentWindAngle(self, apparentWindX: float, apparentWindY: float) -> float: - """Calculate the apparent wind angle based on the carthesian true wind.""" - return angleKeepInterval(cartToArg(apparentWindX, apparentWindY) - self.direction) + """Calculate the apparent wind angle based on the Cartesian true wind.""" + return angleKeepInterval(cartToArg(apparentWindX, apparentWindY) - self.pose[2]) def angleOfAttack(self, apparentWindAngle: float) -> float: """Calculate angle between main sail and apparent wind vector.""" return angleKeepInterval(apparentWindAngle - self.mainSailAngle + pi) def __repr__(self) -> str: - heading: float = round(cartToArg(self.speedX, self.speedY) * 180 / pi, 2) - return f"Boat @({round(self.posX, 3)}|{round(self.posY, 3)}|{heading}°), v={round(self.boatSpeed(), 2)}m/s" + """Generate a descriptive text for the boat.""" + heading: float = round(cartToArg(self.speed[0], self.speed[1]) * 180 / pi, 2) + return f"Boat @({round(self.pose[0], 3)}|{round(self.pose[1], 3)}|{heading}°), v={round(self.boatSpeed(), 2)}m/s" diff --git a/sailsim/boat/FrameList.py b/sailsim/boat/FrameList.py index 02e9df2..9388e47 100644 --- a/sailsim/boat/FrameList.py +++ b/sailsim/boat/FrameList.py @@ -1,5 +1,9 @@ """This module includes everything to store the simulation of a boat.""" +from numpy import ndarray + +from sailsim.utils import Wrench + class Frame(): """This class is holding all data about one frame in the simulation.""" @@ -9,12 +13,8 @@ class Frame(): windX: float windY: float - boatPosX: float - boatPosY: float - boatSpeedX: float - boatSpeedY: float - boatDirection: float - boatAngSpeed: float + pose: ndarray + speed: ndarray boatMainSailAngle: float boatRudderAngle: float @@ -25,26 +25,15 @@ class Frame(): boatLeewayAngle: float boatAngleOfAttack: float - boatForceX: float - boatForceY: float - boatSailDragX: float - boatSailDragY: float - boatSailLiftX: float - boatSailLiftY: float - boatCenterboardDragX: float - boatCenterboardDragY: float - boatCenterboardLiftX: float - boatCenterboardLiftY: float - - boatRudderDragX: float - boatRudderDragY: float - boatRudderLiftX: float - boatRudderLiftY: float - - boatTorque: float - boatWaterDragTorque: float - boatCenterboardTorque: float - boatRudderTorque: float + wrench: Wrench + wrenchSailDrag: Wrench + wrenchSailLift: float + wrenchCenterboardDrag: float + wrenchCenterboardLift: float + wrenchRudderDrag: float + wrenchRudderLift: float + wrenchHullDrag: float + wrenchHullLift: float def collectSimulation(self, simulation) -> None: """Collect and save information about the state of the simulation.""" @@ -53,12 +42,8 @@ def collectSimulation(self, simulation) -> None: def collectBoat(self, boat) -> None: """Collect and save all information about the boat.""" - self.boatPosX = boat.posX - self.boatPosY = boat.posY - self.boatSpeedX = boat.speedX - self.boatSpeedY = boat.speedY - self.boatDirection = boat.direction - self.boatAngSpeed = boat.angSpeed + self.pose = boat.pose + self.speed = boat.speed self.boatMainSailAngle = boat.mainSailAngle self.boatRudderAngle = boat.rudderAngle @@ -68,19 +53,22 @@ def collectBoat(self, boat) -> None: self.boatLeewayAngle = boat.temp_leewayAngle self.boatAngleOfAttack = boat.temp_angleOfAttack - (self.boatForceX, self.boatForceY) = (boat.temp_forceX, boat.temp_forceY) - (self.boatSailDragX, self.boatSailDragY) = (boat.temp_sailDragX, boat.temp_sailDragY) - (self.boatSailLiftX, self.boatSailLiftY) = (boat.temp_sailLiftX, boat.temp_sailLiftY) - (self.boatCenterboardDragX, self.boatCenterboardDragY) = (boat.temp_centerboardDragX, boat.temp_centerboardDragY) - (self.boatCenterboardLiftX, self.boatCenterboardLiftY) = (boat.temp_centerboardLiftX, boat.temp_centerboardLiftY) - - (self.boatRudderDragX, self.boatRudderDragY) = (boat.temp_rudderDragX, boat.temp_rudderDragY) - (self.boatRudderLiftX, self.boatRudderLiftY) = (boat.temp_rudderLiftX, boat.temp_rudderLiftY) - - self.boatTorque = boat.temp_torque - self.boatWaterDragTorque = boat.temp_waterDragTorque - self.boatCenterboardTorque = boat.temp_centerboardTorque - self.boatRudderTorque = boat.temp_rudderTorque + # temp_sailDrag: Wrench + # temp_sailLift: Wrench + # temp_centerboardDrag: Wrench + # temp_centerboardLift: Wrench + # temp_rudderDrag: Wrench + # temp_rudderLift: Wrench + # temp_hullDrag: Wrench + + self.wrench = boat.temp_wrench + self.wrenchSailDrag = boat.temp_sailDrag + self.wrenchSailLift = boat.temp_sailLift + self.wrenchCenterboardDrag = boat.temp_centerboardDrag + self.wrenchCenterboardLift = boat.temp_centerboardLift + self.wrenchRudderDrag = boat.temp_rudderDrag + self.wrenchRudderLift = boat.temp_rudderLift + self.wrenchHullDrag = boat.temp_hullDrag def collectWind(self, wind, x, y) -> None: """Collect and save all information about the wind.""" @@ -88,20 +76,23 @@ def collectWind(self, wind, x, y) -> None: def getCSVLine(self) -> str: """Return string that contains all data about this frame.""" - data = [ - self.frameNr, self.time, - self.boatPosX, self.boatPosY, self.boatSpeedX, self.boatSpeedY, self.boatDirection, self.boatAngSpeed, - self.boatMainSailAngle, self.boatRudderAngle, - self.boatApparentWindX, self.boatApparentWindY, self.boatApparentWindAngle, self.boatLeewayAngle, self.boatAngleOfAttack, - self.boatForceX, self.boatForceY, - self.boatSailDragX, self.boatSailDragY, self.boatSailLiftX, self.boatSailLiftY, - self.boatCenterboardDragX, self.boatCenterboardDragY, self.boatCenterboardLiftX, self.boatCenterboardLiftY, - self.boatRudderDragX, self.boatRudderDragY, self.boatRudderLiftX, self.boatRudderLiftY, - self.boatTorque, self.boatWaterDragTorque, self.boatCenterboardTorque, self.boatRudderTorque, - self.windX, self.windY, - ] - dataStr = [f'{x:.4f}'.rstrip('0').rstrip('.') for x in data] # FIXME very slow and inflexible - return ",".join(dataStr) + # FIXME does not work anymore + # FIXME use csv module + return "Pls fixme" + # data = [ + # self.frameNr, self.time, + # self.pose[0], self.pose[1], self.pose[2], self.speed[0], self.speed[1], self.speed[2], + # self.boatMainSailAngle, self.boatRudderAngle, + # self.boatApparentWindX, self.boatApparentWindY, self.boatApparentWindAngle, self.boatLeewayAngle, self.boatAngleOfAttack, + # self.boatForceX, self.boatForceY, + # self.boatSailDragX, self.boatSailDragY, self.boatSailLiftX, self.boatSailLiftY, + # self.boatCenterboardDragX, self.boatCenterboardDragY, self.boatCenterboardLiftX, self.boatCenterboardLiftY, + # self.boatRudderDragX, self.boatRudderDragY, self.boatRudderLiftX, self.boatRudderLiftY, + # self.boatTorque, self.boatWaterDragTorque, self.boatCenterboardTorque, self.boatRudderTorque, + # self.windX, self.windY, + # ] + # dataStr = [f'{x:.4f}'.rstrip('0').rstrip('.') for x in data] # FIXME very slow and inflexible + # return ",".join(dataStr) class FrameList(): @@ -127,7 +118,7 @@ def reset(self) -> None: def getCoordinateList(self) -> list[tuple[float, float]]: out = [] for frame in self.frames: - out.append((frame.boatPosX, frame.boatPosY)) + out.append((frame.pose[0], frame.pose[1])) return out def getCSV(self) -> str: @@ -141,7 +132,7 @@ def getCSVHeader(self) -> str: """Generate head of .csv file.""" headers = [ "frame", "time", - "boatPosX", "boatPosY", "boatSpeedX", "boatSpeedY", "boatDirection", "boatAngSpeed", + "boatPosX", "boatPosY", "boatDirection", "boatSpeedX", "boatSpeedY", "boatAngSpeed", "boatMainSailAngle", "boatRudderAngle", "boatApparentWindX", "boatApparentWindY", "boatApparentWindAngle", "boatLeewayAngle", "boatAngleOfAttack", "boatForceX", "boatForceY", diff --git a/sailsim/boat/boat_forces.py b/sailsim/boat/boat_forces.py index 0af9181..79ba2d4 100644 --- a/sailsim/boat/boat_forces.py +++ b/sailsim/boat/boat_forces.py @@ -1,67 +1,79 @@ """This module holdes some force calculation for the Boat class.""" -from math import pi +from numpy import pi, ndarray, array, sin from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval from sailsim.utils.coordconversion import polarToCart from sailsim.utils.constants import DENSITY_AIR, DENSITY_WATER +from sailsim.utils import Wrench -def leverSpeedVector(self, lever: float) -> float: - """Calculate the speed vector at a certain point concidering the rotation.""" - (orbSpeedX, orbSpeedY) = polarToCart(self.angSpeed * lever, directionKeepInterval(self.direction + pi)) - return (-self.speedX + orbSpeedX, -self.speedY + orbSpeedY) +def leverSpeedVector(self, lever: float) -> ndarray: + """Calculate the speed vector at a certain point considering the rotation.""" + (orbSpeedX, orbSpeedY) = polarToCart(self.speed[2] * lever, directionKeepInterval(self.pose[2] + pi)) + return array([-self.speed[0] + orbSpeedX, -self.speed[1] + orbSpeedY]) # Sail forces -def sailDrag(self, apparentWindSpeed: float, apparentWindNormX: float, apparentWindNormY: float) -> float: +def sailDrag(self, apparentWindSpeed: float, apparentWindNorm: ndarray) -> Wrench: """Calculate the force that is created when wind blows against the boat.""" - scalarSailDrag = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeed**2 * self.coefficientAirDrag(self.temp_angleOfAttack) - return self.scalarToDragForce(scalarSailDrag, apparentWindNormX, apparentWindNormY) + scalarForce = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeed**2 * self.coefficientAirDrag(self.temp_angleOfAttack) + # print(scalarForce, end="\t") + return Wrench.fromForceAndTorque(apparentWindNorm * scalarForce) + # return self.scalarToDragForce(scalarSailDrag, apparentWindNormX, apparentWindNormY) -def sailLift(self, apparentWindSpeed: float, apparentWindNormX: float, apparentWindNormY: float) -> float: +def sailLift(self, apparentWindSpeed: float, apparentWindNorm: ndarray) -> Wrench: """Calculate the lift force that is created when the wind changes its direction in the sail.""" - scalarSailLift = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeed**2 * self.coefficientAirLift(self.temp_angleOfAttack) - return self.scalarToLiftForce(scalarSailLift, self.temp_angleOfAttack, apparentWindNormX, apparentWindNormY) + scalarForce: float = 0.5 * DENSITY_AIR * self.sailArea * apparentWindSpeed**2 * self.coefficientAirLift(self.temp_angleOfAttack) + # print(scalarForce) + return Wrench.fromForceAndTorque(self.scalarToLiftForce(scalarForce, self.temp_angleOfAttack, apparentWindNorm)) + # return self.scalarToLiftForce(scalarSailLift, self.temp_angleOfAttack, apparentWindNormX, apparentWindNormY) # Centerboard forces -def centerboardDrag(self, flowSpeedSq: float, flowSpeedCenterboardNormX: float, flowSpeedCenterboardNormY: float) -> float: +def centerboardDrag(self, flowSpeedSq: float, flowSpeedCenterboardNorm: ndarray, dirNorm: ndarray) -> Wrench: """Calculate the drag force of the water that is decelerating the boat.""" - scalarCenterboardDrag = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterDrag(self.temp_leewayAngle) - return self.scalarToDragForce(scalarCenterboardDrag, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) + scalarForce = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterDrag(self.temp_leewayAngle) + return Wrench.fromForceAndLever(flowSpeedCenterboardNorm * scalarForce, dirNorm * self.centerboardLever) + # return self.scalarToDragForce(scalarCenterboardDrag, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) -def centerboardLift(self, flowSpeedSq: float, flowSpeedCenterboardNormX: float, flowSpeedCenterboardNormY: float) -> float: +def centerboardLift(self, flowSpeedSq: float, flowSpeedCenterboardNorm: ndarray, dirNorm: ndarray) -> Wrench: """Calculate force that is caused by lift forces in the water.""" - scalarCenterboardLift = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterLift(self.temp_leewayAngle) - return self.scalarToLiftForce(scalarCenterboardLift, self.temp_leewayAngle, flowSpeedCenterboardNormX, flowSpeedCenterboardNormY) + scalarForce = 0.5 * DENSITY_WATER * self.centerboardArea * flowSpeedSq * self.coefficientWaterLift(self.temp_leewayAngle) + return Wrench.fromForceAndLever(self.scalarToLiftForce(scalarForce, self.temp_leewayAngle, flowSpeedCenterboardNorm), dirNorm * self.centerboardLever) # Rudder forces -def rudderDrag(self, flowSpeedSq: float, flowSpeedRudderNormX: float, flowSpeedRudderNormY: float) -> float: +def rudderDrag(self, flowSpeedSq: float, flowSpeedRudderNorm: ndarray, dirNorm: ndarray) -> Wrench: """Calculates Force of the rudder that ist decelerating the boat.""" - scalarRudderDrag = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterDrag(angleKeepInterval(self.temp_leewayAngle + self.rudderAngle)) - return self.scalarToDragForce(scalarRudderDrag, flowSpeedRudderNormX, flowSpeedRudderNormY) + scalarForce = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterDrag(angleKeepInterval(self.temp_leewayAngle + self.rudderAngle)) + return Wrench.fromForceAndLever(flowSpeedRudderNorm * scalarForce, dirNorm * self.rudderLever) -def rudderLift(self, flowSpeedSq: float, flowSpeedRudderNormX: float, flowSpeedRudderNormY: float) -> float: +def rudderLift(self, flowSpeedSq: float, flowSpeedRudderNorm: ndarray, dirNorm: ndarray) -> Wrench: """Calculates Lift caused by rudder.""" - scalarRudderLift = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterLift(angleKeepInterval(self.temp_leewayAngle + self.rudderAngle)) - return self.scalarToLiftForce(scalarRudderLift, angleKeepInterval(self.temp_leewayAngle + self.rudderAngle), flowSpeedRudderNormX, flowSpeedRudderNormY) + scalarForce = 0.5 * DENSITY_WATER * self.rudderArea * flowSpeedSq * self.coefficientWaterLift(angleKeepInterval(self.temp_leewayAngle + self.rudderAngle)) + return Wrench.fromForceAndLever(self.scalarToLiftForce(scalarForce, angleKeepInterval(self.temp_leewayAngle + self.rudderAngle), flowSpeedRudderNorm), dirNorm * self.rudderLever) +# Hull forces +def waterDrag(self) -> Wrench: + """Calculate the wrench of the hull that is cause by drag forces in the water.""" + # TODO add Forces, better approximation + c_w = 1.1 + draught = .3 # bad approximation... + torque = 1 / 64 * c_w * draught * DENSITY_WATER * pow(self.length, 4) * pow(self.speed[2], 2) + if self.speed[2] < 0: + return torque + return -torque # Conversions -def scalarToLiftForce(self, scalarForce: float, angleOfAttack: float, normX: float, normY: float) -> float: - """Convert scalar lift force into cartesian vector""" - if angleOfAttack < 0: - return (-scalarForce * normY, scalarForce * normX) # rotate by 90° counterclockwise - return (scalarForce * normY, -scalarForce * normX) # rotate by 90° clockwise - - -def scalarToDragForce(self, scalarForce: float, normX: float, normY: float) -> float: - """Convert scalar drag force into cartesian vector""" - return (scalarForce * normX, scalarForce * normY) +# TODO move his method somewhere. Could be a static method or a function +def scalarToLiftForce(self, scalarForce: float, angleOfAttack: float, norm: ndarray) -> ndarray: + """Convert scalar lift force into Cartesian vector.""" + if sin(angleOfAttack) < 0: + return array([-scalarForce * norm[1], scalarForce * norm[0]]) # rotate by 90° counterclockwise + return array([scalarForce * norm[1], -scalarForce * norm[0]]) # rotate by 90° clockwise diff --git a/sailsim/boat/boat_getset.py b/sailsim/boat/boat_getset.py index 10fc793..b1f4cb2 100644 --- a/sailsim/boat/boat_getset.py +++ b/sailsim/boat/boat_getset.py @@ -1,21 +1,16 @@ -from math import pi +from numpy import pi, ndarray from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval -def setBoat(self, posX: float, posY: float, direction: float = 0, speedX: float = 0, speedY: float = 0, angSpeed: float = 0) -> float: +def setBoat(self, pose: ndarray, speed: ndarray = ndarray([0, 0, 0])) -> float: """Set important properties of boat.""" - self.posX = posX - self.posY = posY - self.direction = direction - self.speedX = speedX - self.speedY = speedY - self.angSpeed = angSpeed + self.pose = pose + self.speed = speed - -def getPos(self) -> float: +def getPos(self) -> tuple[float, float]: """Return coordinates of the boat.""" - return (self.posX, self.posY) + return (self.pose[0], self.pose[1]) def getSpeed(self) -> float: diff --git a/sailsim/boat/boat_torques.py b/sailsim/boat/boat_torques.py deleted file mode 100644 index ed2e1a1..0000000 --- a/sailsim/boat/boat_torques.py +++ /dev/null @@ -1,21 +0,0 @@ -"""This module calculates torques for the boat class.""" - -from sailsim.utils.constants import DENSITY_AIR, DENSITY_WATER - - -def waterDragTorque(self) -> float: - c_w = 1.1 - draught = .3 # bad approximation... - # TODO use c_w - # print(self.angSpeed) - torque = 1 / 64 * c_w * draught * DENSITY_WATER * pow(self.length, 4) * pow(self.angSpeed, 2) - if self.angSpeed < 0: - return torque - return -torque - - -def centerboardTorque(self, centerboardX: float, centerboardY: float, dirNormX: float, dirNormY: float) -> float: - return (centerboardY * dirNormX - centerboardX * dirNormY) * self.centerboardLever # negative cross product - -def rudderTorque(self, rudderX: float, rudderY: float, dirNormX: float, dirNormY: float) -> float: - return (rudderY * dirNormX - rudderX * dirNormY) * self.rudderLever # negative cross product diff --git a/sailsim/boat/coefficientsapprox.py b/sailsim/boat/coefficientsapprox.py index dfbb63f..73b5fc3 100644 --- a/sailsim/boat/coefficientsapprox.py +++ b/sailsim/boat/coefficientsapprox.py @@ -1,6 +1,6 @@ """These are approximated coefficient for sail boats.""" -from math import pi +from numpy import pi def coefficientAirDrag(angleOfAttack: float) -> float: diff --git a/sailsim/gui/ConfigBoat.py b/sailsim/gui/ConfigBoat.py index af63e07..39d02e5 100644 --- a/sailsim/gui/ConfigBoat.py +++ b/sailsim/gui/ConfigBoat.py @@ -1,6 +1,6 @@ from tkinter import Tk, Frame, Label, Entry, Scale, StringVar, Canvas, Button from tkinter import HORIZONTAL, RIGHT, N, E, S, W -from math import pi, sin, cos +from numpy import pi, sin, cos from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval from sailsim.utils.conversion import stringToFloat diff --git a/sailsim/gui/ConfigWind.py b/sailsim/gui/ConfigWind.py index faa5546..6b0510c 100644 --- a/sailsim/gui/ConfigWind.py +++ b/sailsim/gui/ConfigWind.py @@ -1,5 +1,5 @@ from tkinter import Tk, Frame, Label, Entry, Button, Listbox, StringVar, Canvas, messagebox -from math import pi +from numpy import pi from sailsim.utils.conversion import stringToFloat from sailsim.utils.coordconversion import cartToPolar, polarToCart diff --git a/sailsim/gui/SailsimGUI.py b/sailsim/gui/SailsimGUI.py index 17ad939..4850b1d 100644 --- a/sailsim/gui/SailsimGUI.py +++ b/sailsim/gui/SailsimGUI.py @@ -26,7 +26,7 @@ def __init__(self, simulation): # Load UI from QT generated file self.ui = Ui_MainWindow() self.ui.setupUi(self) - # self.setWindowState(Qt.WindowMaximized) + self.showMaximized() # Playback and timeSlider self.timer = QTimer(self) diff --git a/sailsim/gui/boatInspector.py b/sailsim/gui/boatInspector.py index 129bb91..071c994 100644 --- a/sailsim/gui/boatInspector.py +++ b/sailsim/gui/boatInspector.py @@ -30,7 +30,7 @@ def __init__(self, boat: Boat, parent=None) -> None: self.setBackgroundBrush(QColor(156, 211, 219)) - background = QGraphicsRectItem(-8, -8, 16, 16) + background = QGraphicsRectItem(-32, -32, 64, 64) background.setPen(Qt.NoPen) self.addItem(background) @@ -91,4 +91,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/sailsim/gui/buildgui.bat b/sailsim/gui/buildgui.bat index 8e44419..df049d2 100644 --- a/sailsim/gui/buildgui.bat +++ b/sailsim/gui/buildgui.bat @@ -1,3 +1,5 @@ -call C:\Users\mfbeh\Envs\sailsim\Scripts\activate.bat -pyside6-uic main.ui > qtmain.py +call .env\Scripts\activate.bat +pyside6-uic sailsim\gui\main.ui > sailsim\gui\qtmain.py +autoflake -i --remove-all-unused-imports sailsim\gui\qtmain.py +autopep8 -i -a sailsim\gui\qtmain.py rem pause diff --git a/sailsim/gui/main.ui b/sailsim/gui/main.ui index 297cc38..856bc27 100644 --- a/sailsim/gui/main.ui +++ b/sailsim/gui/main.ui @@ -6,8 +6,8 @@ 0 0 - 963 - 596 + 791 + 641 @@ -31,16 +31,16 @@ - - 1 + + 2 0 - - 3 + + 1 0 @@ -54,7 +54,7 @@ 0 - 1 + 2 @@ -62,12 +62,15 @@ 0 - 2 + 3 QAbstractItemView::NoEditTriggers + + true + true @@ -84,17 +87,17 @@ - Value + X - X + Y - Y + Z @@ -104,7 +107,7 @@ - Position + Pose 0 @@ -116,24 +119,7 @@ 0 - m - - - - - Direction - - - 0 - - - - - - - - - Deg + m, deg @@ -150,24 +136,7 @@ 0 - m/s - - - - - Ang speed - - - 0 - - - - - - - - - deg/s + m/s, deg/s @@ -184,7 +153,7 @@ 0 - N + N, Nm @@ -200,7 +169,7 @@ 0 - N + N, Nm @@ -217,7 +186,7 @@ 0 - N + N, Nm @@ -234,7 +203,7 @@ 0 - N + N, Nm @@ -251,7 +220,7 @@ 0 - N + N, Nm @@ -268,7 +237,7 @@ 0 - N + N, Nm @@ -285,92 +254,41 @@ 0 - N + N, Nm - - - - Torque - - - 0 - - - - - - - - - Nm - - WaterDrag + HullDrag 0 - - - - - - - - Nm - - - - - Centerboard - - - - - - - - - - - - Nm - - - - - Rudder - 0 - - - - + 0 - Nm + N, Nm - Hull + HullLift 0 - + 0 - + 0 - Nm + N, Nm @@ -539,7 +457,7 @@ 0 0 - 963 + 791 26 diff --git a/sailsim/gui/qgraphicsitems.py b/sailsim/gui/qgraphicsitems.py index 4d6130d..4249d27 100644 --- a/sailsim/gui/qgraphicsitems.py +++ b/sailsim/gui/qgraphicsitems.py @@ -1,6 +1,6 @@ """Contains modules for GUI elements.""" -from math import atan2, cos, pi, sin, sqrt +from numpy import arctan2, cos, pi, sin, sqrt from functools import cached_property from typing import Optional, Union @@ -101,8 +101,8 @@ def setFrame(self, framenumber: int) -> None: """ frame = self.frameList[framenumber] if self.allowMovement: - self.setPos(QPointF(frame.boatPosX, -frame.boatPosY)) - self.setRotation(frame.boatDirection / pi * 180) + self.setPos(QPointF(frame.pose[0], -frame.pose[1])) + self.setRotation(frame.pose[2] / pi * 180) self.mainSail.setP2(QPointF(-sin(frame.boatMainSailAngle), cos(frame.boatMainSailAngle)) * 2) self.rudder.setP2(self.rudder.p1() + QPointF(sin(frame.boatRudderAngle), cos(frame.boatRudderAngle)) * 0.5) @@ -150,7 +150,7 @@ def boundingRect(self) -> QRectF: def updateHead(self, scale: float = 1.0) -> None: """Update shape and size of the arrow head.""" line = self.line() - angle = atan2(line.dy(), line.dx()) + angle = arctan2(line.dy(), line.dx()) line1 = QPointF(sin(pi/2 - angle - self.arrowAngle), cos(pi/2 - angle - self.arrowAngle)) line2 = QPointF(sin(pi/2 - angle + self.arrowAngle), cos(pi/2 - angle + self.arrowAngle)) @@ -195,7 +195,7 @@ class GUIBoatVectors(QGraphicsItem): boatForceCenterboardLift = QGraphicsArrowItem() boatForceRudderDrag = QGraphicsArrowItem() boatForceRudderLift = QGraphicsArrowItem() - boatRudderPosition = QGraphicsArrowItem() + wrenchRudderPosition = QGraphicsArrowItem() followBoat = True @@ -257,22 +257,22 @@ def setFrame(self, framenumber: int) -> None: scaleForce = 1 / 1024 if self.followBoat: - self.setPos(QPointF(frame.boatPosX, -frame.boatPosY)) + self.setPos(QPointF(frame.pose[0], -frame.pose[1])) - self.boatSpeed.setP2(QPointF(frame.boatSpeedX, -frame.boatSpeedY)) - self.boatForce.setP2(QPointF(frame.boatForceX, -frame.boatForceY) * scaleForce) + self.boatSpeed.setP2(QPointF(frame.speed[0], -frame.speed[1])) + self.boatForce.setP2(QPointF(frame.wrench[0], -frame.wrench[1]) * scaleForce) - self.boatForceSailDrag.setP2(QPointF(frame.boatSailDragX, -frame.boatSailDragY) * scaleForce) - self.boatForceSailLift.setP2(QPointF(frame.boatSailLiftX, -frame.boatSailLiftY) * scaleForce) + self.boatForceSailDrag.setP2(QPointF(frame.wrenchSailDrag[0], -frame.wrenchSailDrag[1]) * scaleForce) + self.boatForceSailLift.setP2(QPointF(frame.wrenchSailLift[0], -frame.wrenchSailLift[1]) * scaleForce) - self.boatForceCenterboardDrag.setP2(QPointF(frame.boatCenterboardDragX, -frame.boatCenterboardDragY) * scaleForce) - self.boatForceCenterboardLift.setP2(QPointF(frame.boatCenterboardLiftX, -frame.boatCenterboardLiftY) * scaleForce) + self.boatForceCenterboardDrag.setP2(QPointF(frame.wrenchCenterboardDrag[0], -frame.wrenchCenterboardDrag[1]) * scaleForce) + self.boatForceCenterboardLift.setP2(QPointF(frame.wrenchCenterboardLift[0], -frame.wrenchCenterboardLift[1]) * scaleForce) - rudderStartPoint = QPointF(-sin(frame.boatDirection)*2.2, cos(frame.boatDirection)*2.2) + rudderStartPoint = QPointF(-sin(frame.pose[2]) * 2.2, cos(frame.pose[2]) * 2.2) self.boatForceRudderDrag.setLine(QLineF(rudderStartPoint, - rudderStartPoint + QPointF(frame.boatRudderDragX, -frame.boatRudderDragY) * scaleForce)) + rudderStartPoint + QPointF(frame.wrenchRudderDrag[0], -frame.wrenchRudderDrag[1]) * scaleForce)) self.boatForceRudderLift.setLine(QLineF(rudderStartPoint, - rudderStartPoint + QPointF(frame.boatRudderLiftX, -frame.boatRudderLiftY) * scaleForce)) + rudderStartPoint + QPointF(frame.wrenchRudderLift[0], -frame.wrenchRudderLift[1]) * scaleForce)) class GUIWaypoints(QGraphicsItem): diff --git a/sailsim/gui/qtmain.py b/sailsim/gui/qtmain.py index 7aba72e..4842c2a 100644 --- a/sailsim/gui/qtmain.py +++ b/sailsim/gui/qtmain.py @@ -1,35 +1,30 @@ # -*- coding: utf-8 -*- ################################################################################ -## Form generated from reading UI file 'main.ui' +# Form generated from reading UI file 'main.ui' ## -## Created by: Qt User Interface Compiler version 6.2.2 +# Created by: Qt User Interface Compiler version 6.3.1 ## -## WARNING! All changes made in this file will be lost when recompiling UI file! +# WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, - QCursor, QFont, QFontDatabase, QGradient, - QIcon, QImage, QKeySequence, QLinearGradient, - QPainter, QPalette, QPixmap, QRadialGradient, - QTransform) -from PySide6.QtWidgets import (QApplication, QAbstractItemView, QHBoxLayout, QHeaderView, QLabel, - QMainWindow, QMenu, QMenuBar, QSizePolicy, - QSlider, QSplitter, QToolButton, QTreeWidgetItem, - QVBoxLayout, QWidget) +from PySide6.QtCore import (QCoreApplication, QLocale, QMetaObject, QRect, + Qt) +from PySide6.QtGui import (QAction, QCursor) +from PySide6.QtWidgets import (QAbstractItemView, QHBoxLayout, QLabel, QMenu, + QMenuBar, QSizePolicy, QSlider, QSplitter, + QToolButton, QTreeWidgetItem, QVBoxLayout, QWidget) from sailsim.gui.boatInspector import BoatInspectorView from sailsim.gui.mapView import MapViewView from sailsim.gui.valueInspector import ValueInspectorWidget + class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(963, 596) + MainWindow.resize(791, 641) MainWindow.setStyleSheet(u"") self.actionNew = QAction(MainWindow) self.actionNew.setObjectName(u"actionNew") @@ -75,16 +70,16 @@ def setupUi(self, MainWindow): self.main.setHandleWidth(2) self.mapView = MapViewView(self.main) self.mapView.setObjectName(u"mapView") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(1) + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(2) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.mapView.sizePolicy().hasHeightForWidth()) self.mapView.setSizePolicy(sizePolicy) self.main.addWidget(self.mapView) self.right = QSplitter(self.main) self.right.setObjectName(u"right") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy1.setHorizontalStretch(3) + sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + sizePolicy1.setHorizontalStretch(1) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.right.sizePolicy().hasHeightForWidth()) self.right.setSizePolicy(sizePolicy1) @@ -94,15 +89,13 @@ def setupUi(self, MainWindow): self.boatInspector.setObjectName(u"boatInspector") sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(1) + sizePolicy2.setVerticalStretch(2) sizePolicy2.setHeightForWidth(self.boatInspector.sizePolicy().hasHeightForWidth()) self.boatInspector.setSizePolicy(sizePolicy2) self.right.addWidget(self.boatInspector) self.valueInspector = ValueInspectorWidget(self.right) QTreeWidgetItem(self.valueInspector) QTreeWidgetItem(self.valueInspector) - QTreeWidgetItem(self.valueInspector) - QTreeWidgetItem(self.valueInspector) __qtreewidgetitem = QTreeWidgetItem(self.valueInspector) QTreeWidgetItem(__qtreewidgetitem) QTreeWidgetItem(__qtreewidgetitem) @@ -110,22 +103,20 @@ def setupUi(self, MainWindow): QTreeWidgetItem(__qtreewidgetitem) QTreeWidgetItem(__qtreewidgetitem) QTreeWidgetItem(__qtreewidgetitem) + QTreeWidgetItem(__qtreewidgetitem) + QTreeWidgetItem(__qtreewidgetitem) __qtreewidgetitem1 = QTreeWidgetItem(self.valueInspector) QTreeWidgetItem(__qtreewidgetitem1) QTreeWidgetItem(__qtreewidgetitem1) QTreeWidgetItem(__qtreewidgetitem1) - QTreeWidgetItem(__qtreewidgetitem1) - __qtreewidgetitem2 = QTreeWidgetItem(self.valueInspector) - QTreeWidgetItem(__qtreewidgetitem2) - QTreeWidgetItem(__qtreewidgetitem2) - QTreeWidgetItem(__qtreewidgetitem2) self.valueInspector.setObjectName(u"valueInspector") sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(2) + sizePolicy3.setVerticalStretch(3) sizePolicy3.setHeightForWidth(self.valueInspector.sizePolicy().hasHeightForWidth()) self.valueInspector.setSizePolicy(sizePolicy3) self.valueInspector.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.valueInspector.setAlternatingRowColors(True) self.right.addWidget(self.valueInspector) self.valueInspector.header().setCascadingSectionResizes(True) self.valueInspector.header().setMinimumSectionSize(30) @@ -183,14 +174,13 @@ def setupUi(self, MainWindow): self.controlBar.addWidget(self.timeSlider) - self.verticalLayout.addLayout(self.controlBar) self.verticalLayout.setStretch(0, 1) MainWindow.setCentralWidget(self.layout) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 963, 26)) + self.menubar.setGeometry(QRect(0, 0, 791, 26)) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(u"menuFile") self.menuEdit = QMenu(self.menubar) @@ -231,7 +221,7 @@ def setupUi(self, MainWindow): self.timeSlider.valueChanged.connect(MainWindow.updateFrame) self.buttonIncFrame.clicked.connect(MainWindow.incFrame) self.buttonDecFrame.clicked.connect(MainWindow.decFrame) - self.buttonPlay.clicked.connect(MainWindow.pressedPlay) + self.buttonPlay.clicked["bool"].connect(MainWindow.pressedPlay) self.buttonStartFrame.clicked.connect(MainWindow.startFrame) self.buttonEndFrame.clicked.connect(MainWindow.endFrame) self.actionShowWaypointsPathMap.toggled.connect(MainWindow.actionViewShowWaypointsPathMap) @@ -248,15 +238,15 @@ def setupUi(self, MainWindow): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"SailsimGUI", None)) self.actionNew.setText(QCoreApplication.translate("MainWindow", u"New", None)) -#if QT_CONFIG(shortcut) +# if QT_CONFIG(shortcut) self.actionNew.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None)) #endif // QT_CONFIG(shortcut) self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"Open", None)) -#if QT_CONFIG(shortcut) +# if QT_CONFIG(shortcut) self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None)) #endif // QT_CONFIG(shortcut) self.actionOpenGithub.setText(QCoreApplication.translate("MainWindow", u"Open Github", None)) -#if QT_CONFIG(tooltip) +# if QT_CONFIG(tooltip) self.actionOpenGithub.setToolTip(QCoreApplication.translate("MainWindow", u"Open the sailsim repository on Github", None)) #endif // QT_CONFIG(tooltip) self.actionShowWaypointsMap.setText(QCoreApplication.translate("MainWindow", u"Waypoints", None)) @@ -269,106 +259,91 @@ def retranslateUi(self, MainWindow): self.actionShowVectorsMap.setText(QCoreApplication.translate("MainWindow", u"Vectors", None)) self.actionShowBoatPathMap.setText(QCoreApplication.translate("MainWindow", u"Boat path", None)) ___qtreewidgetitem = self.valueInspector.headerItem() - ___qtreewidgetitem.setText(4, QCoreApplication.translate("MainWindow", u"Unit", None)); - ___qtreewidgetitem.setText(3, QCoreApplication.translate("MainWindow", u"Y", None)); - ___qtreewidgetitem.setText(2, QCoreApplication.translate("MainWindow", u"X", None)); - ___qtreewidgetitem.setText(1, QCoreApplication.translate("MainWindow", u"Value", None)); - ___qtreewidgetitem.setText(0, QCoreApplication.translate("MainWindow", u"Name", None)); + ___qtreewidgetitem.setText(4, QCoreApplication.translate("MainWindow", u"Unit", None)) + ___qtreewidgetitem.setText(3, QCoreApplication.translate("MainWindow", u"Z", None)) + ___qtreewidgetitem.setText(2, QCoreApplication.translate("MainWindow", u"Y", None)) + ___qtreewidgetitem.setText(1, QCoreApplication.translate("MainWindow", u"X", None)) + ___qtreewidgetitem.setText(0, QCoreApplication.translate("MainWindow", u"Name", None)) __sortingEnabled = self.valueInspector.isSortingEnabled() self.valueInspector.setSortingEnabled(False) ___qtreewidgetitem1 = self.valueInspector.topLevelItem(0) - ___qtreewidgetitem1.setText(4, QCoreApplication.translate("MainWindow", u"m", None)); - ___qtreewidgetitem1.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem1.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem1.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem1.setText(0, QCoreApplication.translate("MainWindow", u"Position", None)); + ___qtreewidgetitem1.setText(4, QCoreApplication.translate("MainWindow", u"m, deg", None)) + ___qtreewidgetitem1.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem1.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem1.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem1.setText(0, QCoreApplication.translate("MainWindow", u"Pose", None)) ___qtreewidgetitem2 = self.valueInspector.topLevelItem(1) - ___qtreewidgetitem2.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)); - ___qtreewidgetitem2.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem2.setText(0, QCoreApplication.translate("MainWindow", u"Direction", None)); + ___qtreewidgetitem2.setText(4, QCoreApplication.translate("MainWindow", u"m/s, deg/s", None)) + ___qtreewidgetitem2.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem2.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem2.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem2.setText(0, QCoreApplication.translate("MainWindow", u"Speed", None)) ___qtreewidgetitem3 = self.valueInspector.topLevelItem(2) - ___qtreewidgetitem3.setText(4, QCoreApplication.translate("MainWindow", u"m/s", None)); - ___qtreewidgetitem3.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem3.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem3.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem3.setText(0, QCoreApplication.translate("MainWindow", u"Speed", None)); - ___qtreewidgetitem4 = self.valueInspector.topLevelItem(3) - ___qtreewidgetitem4.setText(4, QCoreApplication.translate("MainWindow", u"deg/s", None)); - ___qtreewidgetitem4.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem4.setText(0, QCoreApplication.translate("MainWindow", u"Ang speed", None)); - ___qtreewidgetitem5 = self.valueInspector.topLevelItem(4) - ___qtreewidgetitem5.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem5.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem5.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem5.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem5.setText(0, QCoreApplication.translate("MainWindow", u"Force", None)); - ___qtreewidgetitem6 = ___qtreewidgetitem5.child(0) - ___qtreewidgetitem6.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem6.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem6.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem6.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem6.setText(0, QCoreApplication.translate("MainWindow", u"SailDrag", None)); - ___qtreewidgetitem7 = ___qtreewidgetitem5.child(1) - ___qtreewidgetitem7.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem7.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem7.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem7.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem7.setText(0, QCoreApplication.translate("MainWindow", u"SailLift", None)); - ___qtreewidgetitem8 = ___qtreewidgetitem5.child(2) - ___qtreewidgetitem8.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem8.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem8.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem8.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem8.setText(0, QCoreApplication.translate("MainWindow", u"CenterboardDrag", None)); - ___qtreewidgetitem9 = ___qtreewidgetitem5.child(3) - ___qtreewidgetitem9.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem9.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem9.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem9.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem9.setText(0, QCoreApplication.translate("MainWindow", u"CenterboardLift", None)); - ___qtreewidgetitem10 = ___qtreewidgetitem5.child(4) - ___qtreewidgetitem10.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem10.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem10.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem10.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem10.setText(0, QCoreApplication.translate("MainWindow", u"RudderDrag", None)); - ___qtreewidgetitem11 = ___qtreewidgetitem5.child(5) - ___qtreewidgetitem11.setText(4, QCoreApplication.translate("MainWindow", u"N", None)); - ___qtreewidgetitem11.setText(3, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem11.setText(2, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem11.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem11.setText(0, QCoreApplication.translate("MainWindow", u"RudderLift", None)); - ___qtreewidgetitem12 = self.valueInspector.topLevelItem(5) - ___qtreewidgetitem12.setText(4, QCoreApplication.translate("MainWindow", u"Nm", None)); - ___qtreewidgetitem12.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem12.setText(0, QCoreApplication.translate("MainWindow", u"Torque", None)); + ___qtreewidgetitem3.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem3.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem3.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem3.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem3.setText(0, QCoreApplication.translate("MainWindow", u"Force", None)) + ___qtreewidgetitem4 = ___qtreewidgetitem3.child(0) + ___qtreewidgetitem4.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem4.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem4.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem4.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem4.setText(0, QCoreApplication.translate("MainWindow", u"SailDrag", None)) + ___qtreewidgetitem5 = ___qtreewidgetitem3.child(1) + ___qtreewidgetitem5.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem5.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem5.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem5.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem5.setText(0, QCoreApplication.translate("MainWindow", u"SailLift", None)) + ___qtreewidgetitem6 = ___qtreewidgetitem3.child(2) + ___qtreewidgetitem6.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem6.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem6.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem6.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem6.setText(0, QCoreApplication.translate("MainWindow", u"CenterboardDrag", None)) + ___qtreewidgetitem7 = ___qtreewidgetitem3.child(3) + ___qtreewidgetitem7.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem7.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem7.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem7.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem7.setText(0, QCoreApplication.translate("MainWindow", u"CenterboardLift", None)) + ___qtreewidgetitem8 = ___qtreewidgetitem3.child(4) + ___qtreewidgetitem8.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem8.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem8.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem8.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem8.setText(0, QCoreApplication.translate("MainWindow", u"RudderDrag", None)) + ___qtreewidgetitem9 = ___qtreewidgetitem3.child(5) + ___qtreewidgetitem9.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem9.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem9.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem9.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem9.setText(0, QCoreApplication.translate("MainWindow", u"RudderLift", None)) + ___qtreewidgetitem10 = ___qtreewidgetitem3.child(6) + ___qtreewidgetitem10.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem10.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem10.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem10.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem10.setText(0, QCoreApplication.translate("MainWindow", u"HullDrag", None)) + ___qtreewidgetitem11 = ___qtreewidgetitem3.child(7) + ___qtreewidgetitem11.setText(4, QCoreApplication.translate("MainWindow", u"N, Nm", None)) + ___qtreewidgetitem11.setText(3, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem11.setText(2, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem11.setText(1, QCoreApplication.translate("MainWindow", u"0", None)) + ___qtreewidgetitem11.setText(0, QCoreApplication.translate("MainWindow", u"HullLift", None)) + ___qtreewidgetitem12 = self.valueInspector.topLevelItem(3) + ___qtreewidgetitem12.setText(0, QCoreApplication.translate("MainWindow", u"Angle", None)) ___qtreewidgetitem13 = ___qtreewidgetitem12.child(0) - ___qtreewidgetitem13.setText(4, QCoreApplication.translate("MainWindow", u"Nm", None)); - ___qtreewidgetitem13.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem13.setText(0, QCoreApplication.translate("MainWindow", u"WaterDrag", None)); + ___qtreewidgetitem13.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)) + ___qtreewidgetitem13.setText(0, QCoreApplication.translate("MainWindow", u"Main sail", None)) ___qtreewidgetitem14 = ___qtreewidgetitem12.child(1) - ___qtreewidgetitem14.setText(4, QCoreApplication.translate("MainWindow", u"Nm", None)); - ___qtreewidgetitem14.setText(0, QCoreApplication.translate("MainWindow", u"Centerboard", None)); + ___qtreewidgetitem14.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)) + ___qtreewidgetitem14.setText(0, QCoreApplication.translate("MainWindow", u"Rudder", None)) ___qtreewidgetitem15 = ___qtreewidgetitem12.child(2) - ___qtreewidgetitem15.setText(4, QCoreApplication.translate("MainWindow", u"Nm", None)); - ___qtreewidgetitem15.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem15.setText(0, QCoreApplication.translate("MainWindow", u"Rudder", None)); - ___qtreewidgetitem16 = ___qtreewidgetitem12.child(3) - ___qtreewidgetitem16.setText(4, QCoreApplication.translate("MainWindow", u"Nm", None)); - ___qtreewidgetitem16.setText(1, QCoreApplication.translate("MainWindow", u"0", None)); - ___qtreewidgetitem16.setText(0, QCoreApplication.translate("MainWindow", u"Hull", None)); - ___qtreewidgetitem17 = self.valueInspector.topLevelItem(6) - ___qtreewidgetitem17.setText(0, QCoreApplication.translate("MainWindow", u"Angle", None)); - ___qtreewidgetitem18 = ___qtreewidgetitem17.child(0) - ___qtreewidgetitem18.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)); - ___qtreewidgetitem18.setText(0, QCoreApplication.translate("MainWindow", u"Main sail", None)); - ___qtreewidgetitem19 = ___qtreewidgetitem17.child(1) - ___qtreewidgetitem19.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)); - ___qtreewidgetitem19.setText(0, QCoreApplication.translate("MainWindow", u"Rudder", None)); - ___qtreewidgetitem20 = ___qtreewidgetitem17.child(2) - ___qtreewidgetitem20.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)); - ___qtreewidgetitem20.setText(0, QCoreApplication.translate("MainWindow", u"LeewayAngle", None)); + ___qtreewidgetitem15.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)) + ___qtreewidgetitem15.setText(0, QCoreApplication.translate("MainWindow", u"LeewayAngle", None)) self.valueInspector.setSortingEnabled(__sortingEnabled) self.buttonStartFrame.setText(QCoreApplication.translate("MainWindow", u"\u23ee", None)) diff --git a/sailsim/gui/valueInspector.py b/sailsim/gui/valueInspector.py index 8f1b36b..d981782 100644 --- a/sailsim/gui/valueInspector.py +++ b/sailsim/gui/valueInspector.py @@ -1,52 +1,46 @@ """This module contains the class declaration for the ValueInspectorWidget.""" -from math import pi, sqrt +from numpy import pi, sqrt from PySide6.QtWidgets import QTreeWidget + def toString(text): + """Convert numbers to string.""" return f'{text:.4f}'.rstrip('0').rstrip('.') + class ValueInspectorWidget(QTreeWidget): """List Widget that displays the boat's values.""" def viewFrame(self, frame): - self.updateValueInspectorRow(self.topLevelItem(0), frame.boatPosX, frame.boatPosY) - - self.topLevelItem(1).setText(1, toString(frame.boatDirection * 180 / pi)) - - self.updateValueInspectorRow(self.topLevelItem(2), frame.boatSpeedX, frame.boatSpeedY) + """Display values of a frame given in the value inspector.""" + self.updateValueInspectorRow(self.topLevelItem(0), frame.pose) + self.topLevelItem(0).setText(3, toString(frame.pose[2] * 180 / pi)) - self.topLevelItem(3).setText(1, toString(frame.boatAngSpeed * 180 / pi)) + self.updateValueInspectorRow(self.topLevelItem(1), frame.speed) + self.topLevelItem(1).setText(3, toString(frame.speed[2] * 180 / pi)) # Display Forces - itemForce = self.topLevelItem(4) - self.updateValueInspectorRow(itemForce, frame.boatForceX, frame.boatForceY) - self.updateValueInspectorRow(itemForce.child(0), frame.boatSailDragX, frame.boatSailDragY) - self.updateValueInspectorRow(itemForce.child(1), frame.boatSailLiftX, frame.boatSailLiftY) - self.updateValueInspectorRow(itemForce.child(2), frame.boatCenterboardDragX, frame.boatCenterboardDragY) - self.updateValueInspectorRow(itemForce.child(3), frame.boatCenterboardLiftX, frame.boatCenterboardLiftY) - self.updateValueInspectorRow(itemForce.child(4), frame.boatRudderDragX, frame.boatRudderDragY) - self.updateValueInspectorRow(itemForce.child(5), frame.boatRudderLiftX, frame.boatRudderLiftY) - # self.updateValueInspectorRow(itemForce.child(6), frame.boatHullDragX, frame.boatHullDragY) - # self.updateValueInspectorRow(itemForce.child(7), frame.boatHullLiftX, frame.boatHullLiftY) - - # Display Torque - itemTorque = self.topLevelItem(5) - itemTorque.setText(1, toString(frame.boatTorque)) - itemTorque.child(0).setText(1, toString(frame.boatWaterDragTorque)) - itemTorque.child(1).setText(1, toString(frame.boatCenterboardTorque)) - itemTorque.child(2).setText(1, toString(frame.boatRudderTorque)) - # itemTorque.child(3).setText(1, toString(frame.boatHullTorque)) + itemForce = self.topLevelItem(2) + self.updateValueInspectorRow(itemForce, frame.wrench) + self.updateValueInspectorRow(itemForce.child(0), frame.wrenchSailDrag) + self.updateValueInspectorRow(itemForce.child(1), frame.wrenchSailLift) + self.updateValueInspectorRow(itemForce.child(2), frame.wrenchCenterboardDrag) + self.updateValueInspectorRow(itemForce.child(3), frame.wrenchCenterboardLift) + self.updateValueInspectorRow(itemForce.child(4), frame.wrenchRudderDrag) + self.updateValueInspectorRow(itemForce.child(5), frame.wrenchRudderLift) + # self.updateValueInspectorRow(itemForce.child(6), frame.wrenchHullDrag) + # self.updateValueInspectorRow(itemForce.child(7), frame.wrenchHullLift) # Display Angles - itemAngle = self.topLevelItem(6) + itemAngle = self.topLevelItem(3) itemAngle.child(0).setText(1, toString(frame.boatMainSailAngle)) itemAngle.child(1).setText(1, toString(frame.boatRudderAngle)) itemAngle.child(2).setText(1, toString(frame.boatLeewayAngle)) - def updateValueInspectorRow(self, item, valX, valY): - item.setText(1, toString(sqrt(valX**2 + valY**2))) - item.setText(2, toString(valX)) - item.setText(3, toString(valY)) + def updateValueInspectorRow(self, item, wrench): + item.setText(1, toString(wrench[0])) + item.setText(2, toString(wrench[1])) + item.setText(3, toString(wrench[2])) diff --git a/sailsim/sailor/Commands.py b/sailsim/sailor/Commands.py index 0b23c91..1b08346 100644 --- a/sailsim/sailor/Commands.py +++ b/sailsim/sailor/Commands.py @@ -1,4 +1,4 @@ -from math import sqrt +from numpy import sqrt class Waypoint: diff --git a/sailsim/sailor/Sailor.py b/sailsim/sailor/Sailor.py index 6da8c60..0334d27 100644 --- a/sailsim/sailor/Sailor.py +++ b/sailsim/sailor/Sailor.py @@ -1,4 +1,4 @@ -from math import pi +from numpy import pi from sailsim.utils.coordconversion import polarToCart, cartToArg from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval diff --git a/sailsim/simulation/Simulation.py b/sailsim/simulation/Simulation.py index eee7425..1784dc9 100644 --- a/sailsim/simulation/Simulation.py +++ b/sailsim/simulation/Simulation.py @@ -49,7 +49,7 @@ def step(self) -> None: (boatX, boatY) = self.boat.getPos() # Fetch boat position (windX, windY) = self.wind.getWindCart(boatX, boatY, time) # Get wind self.boat.updateTemporaryData(windX, windY) - (forceX, forceY, torque) = self.boat.resultingCauses() + wrench = self.boat.resultingCauses() # Save frame self.boat.frameList.grabFrame(self, self.boat) @@ -58,7 +58,7 @@ def step(self) -> None: self.boat.runSailor() # Move Boat - self.boat.applyCauses(forceX, forceY, torque, self.timestep) + self.boat.applyCauses(wrench, self.timestep) self.boat.moveInterval(self.timestep) def getTime(self) -> float: diff --git a/sailsim/utils/__init__.py b/sailsim/utils/__init__.py index e69de29..d78bd80 100644 --- a/sailsim/utils/__init__.py +++ b/sailsim/utils/__init__.py @@ -0,0 +1 @@ +from sailsim.utils.wrench import Wrench diff --git a/sailsim/utils/anglecalculations.py b/sailsim/utils/anglecalculations.py index d3e0b83..9d4fa24 100644 --- a/sailsim/utils/anglecalculations.py +++ b/sailsim/utils/anglecalculations.py @@ -1,15 +1,13 @@ """This module is intended for angle and direction calculations.""" -from math import pi +from numpy import pi def angleKeepInterval(angle: float) -> float: """Keep angle inside the range of (-pi; pi].""" - if angle > pi: - return angle - 2 * pi - if angle <= -pi: - return angle + 2 * pi - return angle + if -pi < angle <= pi: + return angle + return (angle + pi) % (2 * pi) - pi def directionKeepInterval(direction: float) -> float: diff --git a/sailsim/utils/coordconversion.py b/sailsim/utils/coordconversion.py index acfeb22..b36ba75 100644 --- a/sailsim/utils/coordconversion.py +++ b/sailsim/utils/coordconversion.py @@ -6,7 +6,7 @@ found on a compass. So this definition differs from the standard definition of angles in mathematics. """ -from math import sin, cos, atan, pi, sqrt +from numpy import sin, cos, arctan2, sqrt def cartToRadius(cartX: float, cartY: float) -> float: @@ -21,22 +21,12 @@ def cartToRadiusSq(cartX: float, cartY: float) -> float: def cartToArg(cartX: float, cartY: float) -> float: """Convert Cartesian coordinates into their corresponding argument (angle).""" - if cartY != 0: - if cartY < 0: # 2nd and 3rd quadrant - return atan(cartX / cartY) + pi - if cartX < 0: # 4st quadrant - return atan(cartX / cartY) + 2 * pi - return atan(cartX / cartY) # else 1th quadrant - if cartX > 0: - return pi / 2 # 90 degrees - if cartX == 0: - return 0 # 0 degrees when speed is 0 - return 3 / 2 * pi # 270 degrees + return arctan2(cartX, cartY) def cartToPolar(cartX: float, cartY: float) -> tuple[float, float]: """Convert Cartesian coordinates into polar coordinates.""" - return (cartToRadius(cartX, cartY), cartToArg(cartX, cartY)) + return (cartToRadius(cartX, cartY), arctan2(cartX, cartY)) def polarToCart(radius: float, argument: float) -> tuple[float, float]: diff --git a/sailsim/utils/wrench.py b/sailsim/utils/wrench.py new file mode 100644 index 0000000..296e977 --- /dev/null +++ b/sailsim/utils/wrench.py @@ -0,0 +1,66 @@ +"""A 2d force vector combined with a 1d torque vector.""" + +from numpy import array, cross, float64, ndarray, append +from numpy.linalg import norm + + +class Wrench(ndarray): + """A 2D force vector combined with a 1D torque vector.""" + + def __new__(cls, vector: ndarray) -> "Wrench": + """ + Create a new Wrench. + + Args: + vector: 3D vector of the wrench + """ + return vector.view(cls) + + @property + def force(self) -> float64: + """Return the scalar force from the wrench.""" + return norm(self[:2]) + + @property + def forceX(self) -> float64: + """ + Return the X force component from the wrench. + + If this method gets called multiple times it can be replaced with `wrench[0]` which is much more performant. + """ + return self[0] + + @property + def forceY(self) -> float64: + """ + Return the Y force component from the wrench. + + If this method gets called multiple times it can be replaced with `wrench[1]` which is much more performant. + """ + return self[1] + + @property + def torque(self) -> float64: + """ + Return the torque from the wrench. + + If this method gets called multiple times it can be replaced with `wrench[2]` which is much more performant. + """ + return self[2] + + @staticmethod + def fromForceAndLever(force: ndarray, lever: ndarray) -> "Wrench": + """Create a Wrench based of two ndarray.""" + return Wrench(append(force, cross(lever, force))) + + @staticmethod + def fromForceAndTorque(force: ndarray, torque: float = 0) -> "Wrench": + """Create a Wrench based of a force.""" + return Wrench(append(force, torque)) + + +if __name__ == "__main__": + w = Wrench(array([3.0, 4.0, 5.0])) + print(w) + print(w.force) + print(w.torque) diff --git a/sailsim/wind/Squall.py b/sailsim/wind/Squall.py index b8775ae..af5dc41 100644 --- a/sailsim/wind/Squall.py +++ b/sailsim/wind/Squall.py @@ -1,4 +1,4 @@ -from math import sin, cos +from numpy import sin, cos from sailsim.utils.coordconversion import cartToArg from sailsim.wind.Windfield import Windfield diff --git a/sailsim/wind/Windfield.py b/sailsim/wind/Windfield.py index 9effcd7..2b0e3da 100644 --- a/sailsim/wind/Windfield.py +++ b/sailsim/wind/Windfield.py @@ -1,4 +1,4 @@ -from math import pi +from numpy import pi from sailsim.utils.coordconversion import cartToPolar diff --git a/setup.cfg b/setup.cfg index 60f4a10..bdbb610 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ python_requires = >=3.6 packages = find: install_requires = opensimplex >= 0.4 + numpy PySide6 [aliases] diff --git a/tests/basictest.py b/tests/basictest.py index 2fce749..73cd1b2 100644 --- a/tests/basictest.py +++ b/tests/basictest.py @@ -1,5 +1,9 @@ """This is a basic simulation program. It is intended to serve as a basis for testing with a know output.""" +# Import modules +from numpy import array +import numpy as np + # Import gui import sys from PySide6.QtWidgets import QApplication @@ -19,13 +23,13 @@ sailor = Sailor(commandListExample) #sailor = Sailor([Waypoint(-30, 30, 1), Waypoint(10, 47, 1), Waypoint(100, 60, 1), Waypoint(0, 100, 2)]) -b = Boat(0, 0, 0) +b = Boat() b.sailor = sailor sailor.importBoat(b) # Create simulation -s = Simulation(b, wind, 0.01, 10000) +s = Simulation(b, wind, 0.01, 1000) # Run simulation try: diff --git a/tests/simulation/test_Simulation.py b/tests/simulation/test_Simulation.py index 3430cf0..e74f342 100644 --- a/tests/simulation/test_Simulation.py +++ b/tests/simulation/test_Simulation.py @@ -2,23 +2,29 @@ from sailsim.simulation.Simulation import Simulation from sailsim.boat.Boat import Boat +from sailsim.sailor.Sailor import Sailor from sailsim.wind.Wind import Wind from sailsim.wind.Windfield import Windfield class TestSimulation(): - def setup(self): - self.s = Simulation(Boat(), Wind([Windfield(0, 1)]), 0.01, 1) + simulation: Simulation - def test_step(self): - self.s.step() + def setup(self) -> None: + b = Boat() + b.sailor = Sailor([]) + b.sailor.importBoat(b) + self.simulation = Simulation(b, Wind([Windfield(0, 1)]), 0.01, 1) - def test_reset(self): - self.s.reset() - assert len(self.s.boat.frameList) == 0 - assert self.s.frame == 0 + def test_step(self) -> None: + self.simulation.step() - def test_run(self): - self.s.run() - assert len(self.s.boat.frameList) == self.s.lastFrame + 1 - assert self.s.frame == self.s.lastFrame + 1 + def test_reset(self) -> None: + self.simulation.reset() + assert len(self.simulation.boat.frameList) == 0 + assert self.simulation.frame == 0 + + def test_run(self) -> None: + self.simulation.run() + assert len(self.simulation.boat.frameList) == self.simulation.lastFrame + 1 + assert self.simulation.frame == self.simulation.lastFrame + 1 diff --git a/tests/utils/test_coordconversion.py b/tests/utils/test_coordconversion.py index 8183a4c..a6be61c 100644 --- a/tests/utils/test_coordconversion.py +++ b/tests/utils/test_coordconversion.py @@ -17,14 +17,14 @@ def test_cartToRadius(): def test_cartToArg(): - assert cartToArg( 0, 1) == approx(0/4 * pi) - assert cartToArg( 1, 1) == approx(1/4 * pi) - assert cartToArg( 1, 0) == approx(2/4 * pi) - assert cartToArg( 1, -1) == approx(3/4 * pi) - assert cartToArg( 0, -1) == approx(4/4 * pi) - assert cartToArg(-1, -1) == approx(5/4 * pi) - assert cartToArg(-1, 0) == approx(6/4 * pi) - assert cartToArg(-1, 1) == approx(7/4 * pi) + assert cartToArg( 0, 1) == approx( 0/4 * pi) + assert cartToArg( 1, 1) == approx( 1/4 * pi) + assert cartToArg( 1, 0) == approx( 2/4 * pi) + assert cartToArg( 1, -1) == approx( 3/4 * pi) + assert cartToArg( 0, -1) == approx( 4/4 * pi) + assert cartToArg(-1, -1) == approx(-3/4 * pi) + assert cartToArg(-1, 0) == approx(-2/4 * pi) + assert cartToArg(-1, 1) == approx(-1/4 * pi) def test_polarToCart(): diff --git a/tests/wind/test_Wind.py b/tests/wind/test_Wind.py index b84533d..a084d6d 100644 --- a/tests/wind/test_Wind.py +++ b/tests/wind/test_Wind.py @@ -24,7 +24,7 @@ def test_getWindCart(self): self.w.winds = [Windfield(2, 1), Windfield(4, 1)] assert self.w.getWindCart(0, 0, 0) == (6, 2) - def test_getWind(self): + def test_getWind(self) -> None: self.w.winds = [Windfield(1, 0)] assert self.w.getWind(0, 0, 0) == approx((1, pi / 2)) @@ -32,4 +32,4 @@ def test_getWind(self): assert self.w.getWind(0, 0, 0) == approx((1, 0)) self.w.winds = [Windfield(-1, 1)] - assert self.w.getWind(0, 0, 0) == approx((sqrt(2), 7/4*pi)) + assert self.w.getWind(0, 0, 0) == approx((sqrt(2), -1 / 4 * pi)) diff --git a/tests/wind/test_Windfield.py b/tests/wind/test_Windfield.py index 56d352a..04c565e 100644 --- a/tests/wind/test_Windfield.py +++ b/tests/wind/test_Windfield.py @@ -22,4 +22,4 @@ def test_getWind(self): assert w.getWind(0, 0, 0) == approx((1, 0)) w = Windfield(-1, 1) - assert w.getWind(0, 0, 0) == approx((sqrt(2), 7/4*pi)) + assert w.getWind(0, 0, 0) == approx((sqrt(2), -1 / 4*pi)) diff --git a/tox.ini b/tox.ini index 9e25a90..ccf5589 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py3.7,py3.9,py3.10 +envlist = py3.7,py3.8,py3.9,py3.10 [testenv] deps =