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 =