From 7ba57ec64401f631d8272cd36c8cef9516c097b5 Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Mon, 27 Jun 2022 10:05:07 +0200 Subject: [PATCH 1/6] use numpy math functions --- sailsim/boat/Boat.py | 2 +- sailsim/boat/boat_forces.py | 2 +- sailsim/boat/boat_getset.py | 2 +- sailsim/boat/coefficientsapprox.py | 2 +- sailsim/gui/ConfigBoat.py | 2 +- sailsim/gui/ConfigWind.py | 2 +- sailsim/gui/qgraphicsitems.py | 4 ++-- sailsim/gui/valueInspector.py | 2 +- sailsim/sailor/Commands.py | 2 +- sailsim/sailor/Sailor.py | 2 +- sailsim/utils/anglecalculations.py | 2 +- sailsim/utils/coordconversion.py | 27 ++++++++++++++------------- sailsim/wind/Squall.py | 2 +- sailsim/wind/Windfield.py | 2 +- tests/simulation/test_Simulation.py | 12 ++++++------ tests/utils/test_coordconversion.py | 16 ++++++++-------- 16 files changed, 42 insertions(+), 41 deletions(-) diff --git a/sailsim/boat/Boat.py b/sailsim/boat/Boat.py index cd87780..4225dcd 100644 --- a/sailsim/boat/Boat.py +++ b/sailsim/boat/Boat.py @@ -1,4 +1,4 @@ -from math import sqrt, pi, sin, cos +from numpy import sqrt, pi, sin, cos from sailsim.boat.FrameList import FrameList from sailsim.sailor.Sailor import Sailor diff --git a/sailsim/boat/boat_forces.py b/sailsim/boat/boat_forces.py index 0af9181..e0ee5ad 100644 --- a/sailsim/boat/boat_forces.py +++ b/sailsim/boat/boat_forces.py @@ -1,6 +1,6 @@ """This module holdes some force calculation for the Boat class.""" -from math import pi +from numpy import pi from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval from sailsim.utils.coordconversion import polarToCart diff --git a/sailsim/boat/boat_getset.py b/sailsim/boat/boat_getset.py index 10fc793..6c10cb7 100644 --- a/sailsim/boat/boat_getset.py +++ b/sailsim/boat/boat_getset.py @@ -1,4 +1,4 @@ -from math import pi +from numpy import pi from sailsim.utils.anglecalculations import angleKeepInterval, directionKeepInterval 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/qgraphicsitems.py b/sailsim/gui/qgraphicsitems.py index 4d6130d..27929e2 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 @@ -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)) diff --git a/sailsim/gui/valueInspector.py b/sailsim/gui/valueInspector.py index 8f1b36b..fbbeda7 100644 --- a/sailsim/gui/valueInspector.py +++ b/sailsim/gui/valueInspector.py @@ -1,6 +1,6 @@ """This module contains the class declaration for the ValueInspectorWidget.""" -from math import pi, sqrt +from numpy import pi, sqrt from PySide6.QtWidgets import QTreeWidget 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/utils/anglecalculations.py b/sailsim/utils/anglecalculations.py index d3e0b83..44b44a8 100644 --- a/sailsim/utils/anglecalculations.py +++ b/sailsim/utils/anglecalculations.py @@ -1,6 +1,6 @@ """This module is intended for angle and direction calculations.""" -from math import pi +from numpy import pi def angleKeepInterval(angle: float) -> float: diff --git a/sailsim/utils/coordconversion.py b/sailsim/utils/coordconversion.py index acfeb22..36d2fc2 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, pi, sqrt def cartToRadius(cartX: float, cartY: float) -> float: @@ -21,22 +21,23 @@ 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) + # 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 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/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/tests/simulation/test_Simulation.py b/tests/simulation/test_Simulation.py index 3430cf0..a28df7f 100644 --- a/tests/simulation/test_Simulation.py +++ b/tests/simulation/test_Simulation.py @@ -8,17 +8,17 @@ class TestSimulation(): def setup(self): - self.s = Simulation(Boat(), Wind([Windfield(0, 1)]), 0.01, 1) + self.simulation = Simulation(Boat(), Wind([Windfield(0, 1)]), 0.01, 1) def test_step(self): - self.s.step() + self.simulation.step() def test_reset(self): self.s.reset() - assert len(self.s.boat.frameList) == 0 - assert self.s.frame == 0 + assert len(self.simulation.boat.frameList) == 0 + assert self.simulation.frame == 0 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 + 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(): From 14186ef2b5fa8a52fe3efc38a518335abf23226e Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Tue, 27 Sep 2022 16:58:51 +0200 Subject: [PATCH 2/6] Basic use of numpy --- sailsim/boat/Boat.py | 58 +++++++++++++------------------- sailsim/boat/FrameList.py | 24 +++++-------- sailsim/boat/boat_forces.py | 4 +-- sailsim/boat/boat_getset.py | 17 ++++------ sailsim/boat/boat_torques.py | 4 +-- sailsim/gui/qgraphicsitems.py | 10 +++--- sailsim/gui/valueInspector.py | 8 ++--- sailsim/simulation/Simulation.py | 4 +-- tests/basictest.py | 8 +++-- 9 files changed, 59 insertions(+), 78 deletions(-) diff --git a/sailsim/boat/Boat.py b/sailsim/boat/Boat.py index 4225dcd..3220dad 100644 --- a/sailsim/boat/Boat.py +++ b/sailsim/boat/Boat.py @@ -1,4 +1,6 @@ -from numpy import sqrt, pi, sin, cos +import numpy as np +from numpy import sqrt, pi, sin, cos, arctan2, array, ndarray +from numpy.linalg import norm from sailsim.boat.FrameList import FrameList from sailsim.sailor.Sailor import Sailor @@ -44,17 +46,13 @@ class Boat: 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 = array([0, 0, 0]), speed: ndarray = array([0, 0, 0])) -> 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 @@ -73,13 +71,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 +93,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: ndarray, 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: @@ -157,10 +146,9 @@ def resultingCauses(self) -> tuple[float, float, float]: flowSpeedCenterboard = sqrt(flowSpeedCenterboardSq) # normalise apparent wind vector and boat speed vector - (dirNormX, dirNormY) = (sin(self.direction), cos(self.direction)) + (dirNormX, dirNormY) = (sin(self.pose[2]), cos(self.pose[2])) # 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 @@ -187,7 +175,7 @@ def resultingCauses(self) -> tuple[float, float, float]: 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) + return array([self.temp_forceX, self.temp_forceY, self.temp_torque]) # Import force and torque functions from sailsim.boat.boat_forces import leverSpeedVector, sailDrag, sailLift, centerboardDrag, centerboardLift, rudderDrag, rudderLift, scalarToDragForce, scalarToLiftForce @@ -195,25 +183,25 @@ def resultingCauses(self) -> tuple[float, float, float]: def boatSpeed(self) -> float: """Return speed of the boat.""" - return sqrt(pow(self.speedX, 2) + pow(self.speedY, 2)) + return 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) + 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" + 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..bf961f8 100644 --- a/sailsim/boat/FrameList.py +++ b/sailsim/boat/FrameList.py @@ -1,5 +1,7 @@ """This module includes everything to store the simulation of a boat.""" +from numpy import ndarray + class Frame(): """This class is holding all data about one frame in the simulation.""" @@ -9,12 +11,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 @@ -53,12 +51,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 @@ -90,7 +84,7 @@ 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.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, @@ -127,7 +121,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 +135,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 e0ee5ad..caaae8a 100644 --- a/sailsim/boat/boat_forces.py +++ b/sailsim/boat/boat_forces.py @@ -9,8 +9,8 @@ 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) + (orbSpeedX, orbSpeedY) = polarToCart(self.speed[2] * lever, directionKeepInterval(self.pose[2] + pi)) + return (-self.speed[0] + orbSpeedX, -self.speed[1] + orbSpeedY) # Sail forces diff --git a/sailsim/boat/boat_getset.py b/sailsim/boat/boat_getset.py index 6c10cb7..b1f4cb2 100644 --- a/sailsim/boat/boat_getset.py +++ b/sailsim/boat/boat_getset.py @@ -1,21 +1,16 @@ -from numpy 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 index ed2e1a1..32d9db1 100644 --- a/sailsim/boat/boat_torques.py +++ b/sailsim/boat/boat_torques.py @@ -8,8 +8,8 @@ def waterDragTorque(self) -> float: 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: + 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 diff --git a/sailsim/gui/qgraphicsitems.py b/sailsim/gui/qgraphicsitems.py index 27929e2..04eb959 100644 --- a/sailsim/gui/qgraphicsitems.py +++ b/sailsim/gui/qgraphicsitems.py @@ -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) @@ -257,9 +257,9 @@ 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.boatSpeed.setP2(QPointF(frame.speed[0], -frame.speed[1])) self.boatForce.setP2(QPointF(frame.boatForceX, -frame.boatForceY) * scaleForce) self.boatForceSailDrag.setP2(QPointF(frame.boatSailDragX, -frame.boatSailDragY) * scaleForce) @@ -268,7 +268,7 @@ def setFrame(self, framenumber: int) -> None: self.boatForceCenterboardDrag.setP2(QPointF(frame.boatCenterboardDragX, -frame.boatCenterboardDragY) * scaleForce) self.boatForceCenterboardLift.setP2(QPointF(frame.boatCenterboardLiftX, -frame.boatCenterboardLiftY) * 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)) self.boatForceRudderLift.setLine(QLineF(rudderStartPoint, diff --git a/sailsim/gui/valueInspector.py b/sailsim/gui/valueInspector.py index fbbeda7..a4f3c1b 100644 --- a/sailsim/gui/valueInspector.py +++ b/sailsim/gui/valueInspector.py @@ -11,13 +11,13 @@ 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.updateValueInspectorRow(self.topLevelItem(0), frame.pose[0], frame.pose[1]) - self.topLevelItem(1).setText(1, toString(frame.boatDirection * 180 / pi)) + self.topLevelItem(1).setText(1, toString(frame.pose[2] * 180 / pi)) - self.updateValueInspectorRow(self.topLevelItem(2), frame.boatSpeedX, frame.boatSpeedY) + self.updateValueInspectorRow(self.topLevelItem(2), frame.speed[0], frame.speed[1]) - self.topLevelItem(3).setText(1, toString(frame.boatAngSpeed * 180 / pi)) + self.topLevelItem(3).setText(1, toString(frame.speed[2] * 180 / pi)) # Display Forces itemForce = self.topLevelItem(4) 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/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: From e3780320b96c627f3b1fc4d44d53461248b34ebf Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Thu, 6 Oct 2022 16:45:47 +0200 Subject: [PATCH 3/6] Introduced wrenches --- sailsim/boat/Boat.py | 79 ++++++---------- sailsim/boat/FrameList.py | 91 +++++++++--------- sailsim/boat/boat_forces.py | 76 ++++++++------- sailsim/gui/main.ui | 146 +++++++---------------------- sailsim/gui/qgraphicsitems.py | 18 ++-- sailsim/gui/qtmain.py | 144 +++++++++++++--------------- sailsim/gui/valueInspector.py | 52 +++++----- sailsim/utils/__init__.py | 1 + sailsim/utils/anglecalculations.py | 8 +- sailsim/utils/coordconversion.py | 17 +--- sailsim/utils/wrench.py | 66 +++++++++++++ 11 files changed, 320 insertions(+), 378 deletions(-) create mode 100644 sailsim/utils/wrench.py diff --git a/sailsim/boat/Boat.py b/sailsim/boat/Boat.py index 3220dad..7592151 100644 --- a/sailsim/boat/Boat.py +++ b/sailsim/boat/Boat.py @@ -1,11 +1,13 @@ -import numpy as np -from numpy import sqrt, pi, sin, cos, arctan2, array, ndarray +"""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.coordconversion import cartToRadiusSq, cartToArg +from sailsim.utils import Wrench from sailsim.boat.coefficientsapprox import coefficientAirDrag, coefficientAirLift, coefficientWaterDrag, coefficientWaterLift @@ -24,29 +26,18 @@ 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, pose: ndarray = array([0, 0, 0]), speed: ndarray = array([0, 0, 0])) -> None: + def __init__(self, pose: ndarray = zeros(3), speed: ndarray = zeros(3)) -> None: """ Create a boat. @@ -93,7 +84,7 @@ def __init__(self, pose: ndarray = array([0, 0, 0]), speed: ndarray = array([0, self.frameList: FrameList = FrameList() # Simulation methods - def applyCauses(self, wrench: ndarray, 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 @@ -134,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) @@ -145,41 +136,33 @@ 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.pose[2]), cos(self.pose[2])) - # 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 - (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) # normalised apparent wind vector + flowSpeedRudderNorm: ndarray = array([flowSpeedRudderX, flowSpeedRudderY]) / flowSpeedRudder if not flowSpeedRudder == 0 else zeros(2) # normalised speed vector + flowSpeedCenterboardNorm: ndarray = array([flowSpeedCenterboardX, flowSpeedCenterboardY]) / flowSpeedCenterboard if not flowSpeedCenterboard == 0 else zeros(2) # normalised 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 array([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 + 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.""" diff --git a/sailsim/boat/FrameList.py b/sailsim/boat/FrameList.py index bf961f8..9388e47 100644 --- a/sailsim/boat/FrameList.py +++ b/sailsim/boat/FrameList.py @@ -2,6 +2,8 @@ from numpy import ndarray +from sailsim.utils import Wrench + class Frame(): """This class is holding all data about one frame in the simulation.""" @@ -23,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.""" @@ -62,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.""" @@ -82,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.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) + # 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(): diff --git a/sailsim/boat/boat_forces.py b/sailsim/boat/boat_forces.py index caaae8a..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 numpy 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.""" +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 (-self.speed[0] + orbSpeedX, -self.speed[1] + orbSpeedY) + 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/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 04eb959..4249d27 100644 --- a/sailsim/gui/qgraphicsitems.py +++ b/sailsim/gui/qgraphicsitems.py @@ -195,7 +195,7 @@ class GUIBoatVectors(QGraphicsItem): boatForceCenterboardLift = QGraphicsArrowItem() boatForceRudderDrag = QGraphicsArrowItem() boatForceRudderLift = QGraphicsArrowItem() - boatRudderPosition = QGraphicsArrowItem() + wrenchRudderPosition = QGraphicsArrowItem() followBoat = True @@ -260,19 +260,19 @@ def setFrame(self, framenumber: int) -> None: self.setPos(QPointF(frame.pose[0], -frame.pose[1])) self.boatSpeed.setP2(QPointF(frame.speed[0], -frame.speed[1])) - self.boatForce.setP2(QPointF(frame.boatForceX, -frame.boatForceY) * scaleForce) + 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.pose[2])*2.2, cos(frame.pose[2])*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..4376ada 100644 --- a/sailsim/gui/qtmain.py +++ b/sailsim/gui/qtmain.py @@ -3,7 +3,7 @@ ################################################################################ ## 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! ################################################################################ @@ -16,10 +16,10 @@ 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.QtWidgets import (QAbstractItemView, QApplication, QHBoxLayout, QHeaderView, + QLabel, QMainWindow, QMenu, QMenuBar, + QSizePolicy, QSlider, QSplitter, QToolButton, + QTreeWidgetItem, QVBoxLayout, QWidget) from sailsim.gui.boatInspector import BoatInspectorView from sailsim.gui.mapView import MapViewView @@ -29,7 +29,7 @@ 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 +75,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 +94,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 +108,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) @@ -190,7 +186,7 @@ def setupUi(self, MainWindow): 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 +227,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) @@ -270,105 +266,90 @@ def retranslateUi(self, MainWindow): 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(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(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"Position", 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(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"Direction", 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(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"Speed", None)); - ___qtreewidgetitem4 = self.valueInspector.topLevelItem(3) - ___qtreewidgetitem4.setText(4, QCoreApplication.translate("MainWindow", u"deg/s", 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"Ang speed", None)); - ___qtreewidgetitem5 = self.valueInspector.topLevelItem(4) - ___qtreewidgetitem5.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"Force", None)); - ___qtreewidgetitem6 = ___qtreewidgetitem5.child(0) - ___qtreewidgetitem6.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"SailDrag", None)); - ___qtreewidgetitem7 = ___qtreewidgetitem5.child(1) - ___qtreewidgetitem7.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"SailLift", None)); - ___qtreewidgetitem8 = ___qtreewidgetitem5.child(2) - ___qtreewidgetitem8.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"CenterboardDrag", None)); - ___qtreewidgetitem9 = ___qtreewidgetitem5.child(3) - ___qtreewidgetitem9.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"CenterboardLift", None)); - ___qtreewidgetitem10 = ___qtreewidgetitem5.child(4) - ___qtreewidgetitem10.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"RudderDrag", None)); - ___qtreewidgetitem11 = ___qtreewidgetitem5.child(5) - ___qtreewidgetitem11.setText(4, QCoreApplication.translate("MainWindow", u"N", 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"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)); + ___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)) @@ -383,3 +364,4 @@ def retranslateUi(self, MainWindow): self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) self.menuTools.setTitle(QCoreApplication.translate("MainWindow", u"Tools", None)) # retranslateUi + diff --git a/sailsim/gui/valueInspector.py b/sailsim/gui/valueInspector.py index a4f3c1b..d981782 100644 --- a/sailsim/gui/valueInspector.py +++ b/sailsim/gui/valueInspector.py @@ -4,49 +4,43 @@ 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.pose[0], frame.pose[1]) - - self.topLevelItem(1).setText(1, toString(frame.pose[2] * 180 / pi)) - - self.updateValueInspectorRow(self.topLevelItem(2), frame.speed[0], frame.speed[1]) + """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.speed[2] * 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/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 44b44a8..9d4fa24 100644 --- a/sailsim/utils/anglecalculations.py +++ b/sailsim/utils/anglecalculations.py @@ -5,11 +5,9 @@ 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 36d2fc2..742fc1d 100644 --- a/sailsim/utils/coordconversion.py +++ b/sailsim/utils/coordconversion.py @@ -6,7 +6,9 @@ found on a compass. So this definition differs from the standard definition of angles in mathematics. """ -from numpy import sin, cos, arctan2, pi, sqrt +from numpy import sin, cos, arctan2, sqrt + +from sailsim.utils.anglecalculations import angleKeepInterval def cartToRadius(cartX: float, cartY: float) -> float: @@ -21,18 +23,7 @@ def cartToRadiusSq(cartX: float, cartY: float) -> float: def cartToArg(cartX: float, cartY: float) -> float: """Convert Cartesian coordinates into their corresponding argument (angle).""" - return arctan2(cartX, cartY) - # 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 angleKeepInterval(arctan2(cartX, cartY)) def cartToPolar(cartX: float, cartY: 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) From d0cdf8e7d1992e2d0b4857c45a81e390a060016f Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Thu, 6 Oct 2022 16:56:29 +0200 Subject: [PATCH 4/6] Cleanup --- .vscode/settings.json | 12 ++++++++++++ sailsim/boat/Boat.py | 17 +++++++++-------- sailsim/boat/boat_torques.py | 21 --------------------- sailsim/gui/SailsimGUI.py | 2 +- sailsim/gui/boatInspector.py | 4 ++-- tests/simulation/test_Simulation.py | 20 +++++++++++++------- 6 files changed, 37 insertions(+), 39 deletions(-) delete mode 100644 sailsim/boat/boat_torques.py 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/sailsim/boat/Boat.py b/sailsim/boat/Boat.py index 7592151..50dc87f 100644 --- a/sailsim/boat/Boat.py +++ b/sailsim/boat/Boat.py @@ -5,7 +5,7 @@ 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 @@ -49,7 +49,7 @@ def __init__(self, pose: ndarray = zeros(3), speed: ndarray = zeros(3)) -> None: 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 @@ -139,9 +139,9 @@ def resultingCauses(self) -> Wrench: # 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) # normalised apparent wind vector - flowSpeedRudderNorm: ndarray = array([flowSpeedRudderX, flowSpeedRudderY]) / flowSpeedRudder if not flowSpeedRudder == 0 else zeros(2) # normalised speed vector - flowSpeedCenterboardNorm: ndarray = array([flowSpeedCenterboardX, flowSpeedCenterboardY]) / flowSpeedCenterboard if not flowSpeedCenterboard == 0 else zeros(2) # normalised speed vector + 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_sailDrag = self.sailDrag(self.temp_apparentWindSpeed, apparentWindNorm) @@ -158,7 +158,7 @@ def resultingCauses(self) -> Wrench: # TODO Hull forces self.temp_hullDrag = self.waterDrag() - self.temp_wrench = self.temp_sailDrag + self.temp_sailLift + self.temp_centerboardDrag + self.temp_centerboardLift + self.temp_rudderDrag + self.temp_rudderLift + self.temp_hullDrag + 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 @@ -166,7 +166,7 @@ def resultingCauses(self) -> Wrench: def boatSpeed(self) -> float: """Return speed of the boat.""" - return norm(self.speed[:2]) + return float(norm(self.speed[:2])) # Angle calculations def calcLeewayAngle(self) -> float: @@ -178,7 +178,7 @@ def apparentWind(self, trueWindX: float, trueWindY: float) -> tuple[float, float 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.""" + """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: @@ -186,5 +186,6 @@ def angleOfAttack(self, apparentWindAngle: float) -> float: return angleKeepInterval(apparentWindAngle - self.mainSailAngle + pi) def __repr__(self) -> str: + """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/boat_torques.py b/sailsim/boat/boat_torques.py deleted file mode 100644 index 32d9db1..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.speed[2], 2) - if self.speed[2] < 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/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/tests/simulation/test_Simulation.py b/tests/simulation/test_Simulation.py index a28df7f..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.simulation = Simulation(Boat(), Wind([Windfield(0, 1)]), 0.01, 1) + simulation: Simulation - def test_step(self): + 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_step(self) -> None: self.simulation.step() - def test_reset(self): - self.s.reset() + def test_reset(self) -> None: + self.simulation.reset() assert len(self.simulation.boat.frameList) == 0 assert self.simulation.frame == 0 - def test_run(self): - self.s.run() + 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 From 810627346be46fd210a0f2c8aed7f4ac156f8016 Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Sat, 8 Oct 2022 11:03:06 +0200 Subject: [PATCH 5/6] Fix testing --- requirements.txt | 1 + sailsim/utils/coordconversion.py | 4 +--- setup.cfg | 1 + tests/wind/test_Wind.py | 4 ++-- tests/wind/test_Windfield.py | 2 +- tox.ini | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) 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/sailsim/utils/coordconversion.py b/sailsim/utils/coordconversion.py index 742fc1d..b36ba75 100644 --- a/sailsim/utils/coordconversion.py +++ b/sailsim/utils/coordconversion.py @@ -8,8 +8,6 @@ from numpy import sin, cos, arctan2, sqrt -from sailsim.utils.anglecalculations import angleKeepInterval - def cartToRadius(cartX: float, cartY: float) -> float: """Convert Cartesian coordinates into their corresponding radius.""" @@ -23,7 +21,7 @@ def cartToRadiusSq(cartX: float, cartY: float) -> float: def cartToArg(cartX: float, cartY: float) -> float: """Convert Cartesian coordinates into their corresponding argument (angle).""" - return angleKeepInterval(arctan2(cartX, cartY)) + return arctan2(cartX, cartY) def cartToPolar(cartX: float, cartY: float) -> tuple[float, float]: 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/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 = From 6d76b68964cc046e98de4414714bea4bdde2e76c Mon Sep 17 00:00:00 2001 From: Michael Behrens Date: Sat, 8 Oct 2022 11:27:16 +0200 Subject: [PATCH 6/6] Better qtmain.py --- requirements_dev.txt | 1 + sailsim/gui/buildgui.bat | 6 +- sailsim/gui/qtmain.py | 167 +++++++++++++++++++-------------------- 3 files changed, 85 insertions(+), 89 deletions(-) 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/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/qtmain.py b/sailsim/gui/qtmain.py index 4376ada..4842c2a 100644 --- a/sailsim/gui/qtmain.py +++ b/sailsim/gui/qtmain.py @@ -1,30 +1,25 @@ # -*- 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.3.1 +# 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 (QAbstractItemView, QApplication, 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(): @@ -179,7 +174,6 @@ def setupUi(self, MainWindow): self.controlBar.addWidget(self.timeSlider) - self.verticalLayout.addLayout(self.controlBar) self.verticalLayout.setStretch(0, 1) @@ -244,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)) @@ -265,91 +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"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)); + ___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, 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)); + ___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"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)); + ___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"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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___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)); + ___qtreewidgetitem12.setText(0, QCoreApplication.translate("MainWindow", u"Angle", None)) ___qtreewidgetitem13 = ___qtreewidgetitem12.child(0) - ___qtreewidgetitem13.setText(4, QCoreApplication.translate("MainWindow", u"Deg", None)); - ___qtreewidgetitem13.setText(0, QCoreApplication.translate("MainWindow", u"Main sail", 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"Deg", None)); - ___qtreewidgetitem14.setText(0, QCoreApplication.translate("MainWindow", u"Rudder", 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"Deg", None)); - ___qtreewidgetitem15.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)) @@ -364,4 +358,3 @@ def retranslateUi(self, MainWindow): self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) self.menuTools.setTitle(QCoreApplication.translate("MainWindow", u"Tools", None)) # retranslateUi -