diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index 840711b52..426d58af7 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -35,4 +35,5 @@ from shipHydrostatics.Tools import floatingArea, BMT, mainFrameCoeff from shipCreateWeight.Tools import createWeight from shipCreateTank.Tools import createTank from shipCapacityCurve.Tools import tankCapacityCurve -from shipCreateLoadCondition.Tools import createLoadCondition \ No newline at end of file +from shipCreateLoadCondition.Tools import createLoadCondition +from shipGZ.Tools import gz \ No newline at end of file diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index c6bb90ccf..053259b35 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -154,7 +154,8 @@ class Tank: return ret_value - def getCoG(self, fp, vol, roll=0.0, trim=0.0): + def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): """Return the fluid volume center of gravity, provided the volume of fluid inside the tank. @@ -162,15 +163,14 @@ class Tank: Keyword arguments: fp -- Part::FeaturePython object affected. - vol -- Volume of fluid (in m^3). - roll -- Ship roll angle (in degrees). - trim -- Ship trim angle (in degrees). + vol -- Volume of fluid. + roll -- Ship roll angle. + trim -- Ship trim angle. If the fluid volume is bigger than the total tank one, it will be conveniently clamped. """ # Change the units of the volume, and clamp the value - vol = vol * Units.Metre.Value**3 if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: @@ -187,19 +187,19 @@ class Tank: return cog # Get a first estimation of the level - level = vol / fp.Shape.Volume + level = vol.Value / fp.Shape.Volume # Transform the tank shape current_placement = fp.Placement m = current_placement.toMatrix() - m.rotateX(radians(roll)) - m.rotateY(-radians(trim)) - fp.Placement = m + m.rotateX(roll.getValueAs("rad")) + m.rotateY(-trim.getValueAs("rad")) + fp.Placement = Placement(m) # Iterate to find the fluid shape for i in range(COMMON_BOOLEAN_ITERATIONS): shape = self.getVolume(fp, level, return_shape=True) - error = (vol - shape.Volume) / fp.Shape.Volume + error = (vol.Value - shape.Volume) / fp.Shape.Volume if abs(error) < 0.01: break level += error @@ -222,8 +222,8 @@ class Tank: fp.Placement = current_placement p = Part.Point(cog) m = Matrix() - m.rotateY(radians(trim)) - m.rotateX(-radians(roll)) + m.rotateY(trim.getValueAs("rad")) + m.rotateX(-roll.getValueAs("rad")) p.rotate(Placement(m)) return Vector(p.X, p.Y, p.Z) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 1af0aa833..c5dc76255 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -48,18 +48,22 @@ class TaskPanel: form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") - rolls = [] - roll = Units.Quantity(Locale.fromString( - form.angle.text())).getValueAs('deg').Value + roll = Units.Quantity(Locale.fromString(form.angle.text())) n_points = form.n_points.value() + var_trim = form.var_trim.isChecked() + + rolls = [] for i in range(n_points): rolls.append(roll * i / float(n_points - 1)) - gzs, drafts, trims = Tools.solve(self.ship, - self.weights, - self.tanks, - rolls, - form.var_trim.isChecked()) + points = Tools.gz(self.lc, rolls, var_trim) + gzs = [] + drafts = [] + trims = [] + for p in points: + gzs.append(p[0].getValueAs('m').Value) + drafts.append(p[1].getValueAs('m').Value) + trims.append(p[2].getValueAs('deg').Value) PlotAux.Plot(rolls, gzs, drafts, trims) @@ -175,99 +179,6 @@ class TaskPanel: continue except ValueError: continue - # Extract the weights and the tanks - weights = [] - index = 6 - while True: - try: - ws = doc.getObjectsByLabel(obj.get('A{}'.format(index))) - except ValueError: - break - index += 1 - if len(ws) != 1: - if len(ws) == 0: - msg = QtGui.QApplication.translate( - "ship_console", - "Wrong Weight label! (no instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - obj.get('A{}'.format(index - 1)))) - else: - msg = QtGui.QApplication.translate( - "ship_console", - "Ambiguous Weight label! ({} instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('A{}'.format(index - 1)))) - continue - w = ws[0] - try: - if w is None or not w.PropertiesList.index("IsWeight"): - msg = QtGui.QApplication.translate( - "ship_console", - "Invalid Weight! (the object labeled as" - "'{}' is not a weight)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('A{}'.format(index - 1)))) - continue - except ValueError: - continue - weights.append(w) - tanks = [] - index = 6 - while True: - try: - ts = doc.getObjectsByLabel(obj.get('C{}'.format(index))) - dens = float(obj.get('D{}'.format(index))) - level = float(obj.get('E{}'.format(index))) - except ValueError: - break - index += 1 - if len(ts) != 1: - if len(ts) == 0: - msg = QtGui.QApplication.translate( - "ship_console", - "Wrong Tank label! (no instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - obj.get('C{}'.format(index - 1)))) - else: - msg = QtGui.QApplication.translate( - "ship_console", - "Ambiguous Tank label! ({} instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ts), - obj.get('C{}'.format(index - 1)))) - continue - t = ts[0] - try: - if t is None or not t.PropertiesList.index("IsTank"): - msg = QtGui.QApplication.translate( - "ship_console", - "Invalid Tank! (the object labeled as" - "'{}' is not a tank)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('C{}'.format(index - 1)))) - continue - except ValueError: - continue - tanks.append((t, dens, level)) # Let's see if several loading conditions have been selected (and # prompt a warning) if self.lc: @@ -281,8 +192,6 @@ class TaskPanel: break self.lc = obj self.ship = ship - self.weights = weights - self.tanks = tanks if not self.lc: msg = QtGui.QApplication.translate( "ship_console", diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index a090b1ed3..c3bad14b2 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -22,72 +22,79 @@ #*************************************************************************** import math +import FreeCAD as App +import FreeCADGui as Gui from FreeCAD import Vector, Matrix, Placement import Part import Units -import FreeCAD as App -import FreeCADGui as Gui import Instance as ShipInstance import WeightInstance import TankInstance from shipHydrostatics import Tools as Hydrostatics -G = 9.81 +G = Units.parseQuantity("9.81 m/s^2") MAX_EQUILIBRIUM_ITERS = 10 -DENS = 1.025 # [tons/m3], salt water +DENS = Units.parseQuantity("1025 kg/m^3") TRIM_RELAX_FACTOR = 10.0 def solve(ship, weights, tanks, rolls, var_trim=True): - """ Compute the ship GZ curve. - @param ship Ship instance. - @param weights Considered weights. - @param tanks Considered tanks. - @param rolls List of considered roll angles. - @param var_trim True if the trim angle should be recomputed at each roll - angle, False otherwise. - @return GZ values, drafts and trim angles, for each roll angle (in 3 - separated lists) + """Compute the ship GZ stability curve + + Position arguments: + ship -- Ship object + weights -- List of weights to consider + tanks -- List of tanks to consider (each one should be a tuple with the + tank instance, the density of the fluid inside, and the filling level ratio) + rolls -- List of roll angles + + Keyword arguments: + var_trim -- True if the equilibrium trim should be computed for each roll + angle, False if null trim angle can be used instead. + + Returned value: + List of GZ curve points. Each point contains the GZ stability length, the + equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is + False) """ # Get the unloaded weight (ignoring the tanks for the moment). - W = 0.0 - COG = Vector() + W = Units.parseQuantity("0 kg") + mom_x = Units.parseQuantity("0 kg*m") + mom_y = Units.parseQuantity("0 kg*m") + mom_z = Units.parseQuantity("0 kg*m") for w in weights: - W += w.Proxy.getMass(w).getValueAs('kg').Value + W += w.Proxy.getMass(w) m = w.Proxy.getMoment(w) - COG.x += m[0].getValueAs('kg*m').Value - COG.y += m[1].getValueAs('kg*m').Value - COG.z += m[2].getValueAs('kg*m').Value - COG = COG.multiply(1.0 / W) + mom_x += m[0] + mom_y += m[1] + mom_z += m[2] + COG = Vector(mom_x / W, mom_y / W, mom_z / W) W = W * G # Get the tanks weight - TW = 0.0 + TW = Units.parseQuantity("0 kg") VOLS = [] for t in tanks: # t[0] = tank object # t[1] = load density # t[2] = filling level - vol = t[0].Proxy.getVolume(t[0], t[2]).getValueAs('m^3').Value + vol = t[0].Proxy.getVolume(t[0], t[2]) VOLS.append(vol) TW += vol * t[1] TW = TW * G - gzs = [] - drafts = [] - trims = [] + points = [] for i,roll in enumerate(rolls): App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls))) - gz, draft, trim = solve_point(W, COG, TW, VOLS, - ship, tanks, roll, var_trim) - if gz is None: + point = solve_point(W, COG, TW, VOLS, + ship, tanks, roll, var_trim) + if point is None: return [] - gzs.append(gz) - drafts.append(draft) - trims.append(trim) + points.append(point) + + return points - return gzs, drafts, trims def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): """ Compute the ship GZ value. @@ -101,56 +108,209 @@ def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): angle, False otherwise. @return GZ value, equilibrium draft, and equilibrium trim angle (0 if variable trim has not been requested) - """ - gz = 0.0 - + """ # Look for the equilibrium draft (and eventually the trim angle too) - max_draft = ship.Shape.BoundBox.ZMax / Units.Metre.Value - draft = ship.Draft.getValueAs('m').Value - max_disp = ship.Shape.Volume / Units.Metre.Value**3 * DENS * 1000.0 * G + max_draft = Units.Quantity(ship.Shape.BoundBox.ZMax, Units.Length) + draft = ship.Draft + max_disp = Units.Quantity(ship.Shape.Volume, Units.Volume) * DENS * G if max_disp < W + TW: msg = QtGui.QApplication.translate( "ship_console", "Too much weight! The ship will never displace water enough", None, QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + ' ({} tons vs. {} tons)\n'.format( - max_disp / 1000.0 / G, (W + TW) / 1000.0 / G)) + App.Console.PrintError(msg + ' ({} vs. {})\n'.format( + (max_disp / G).UserString, ((W + TW) / G).UserString)) return None - trim = 0.0 + trim = Units.parseQuantity("0 deg") for i in range(MAX_EQUILIBRIUM_ITERS): # Get the displacement, and the bouyance application point disp, B, _ = Hydrostatics.displacement(ship, - draft * Units.Metre, - roll * Units.Degree, - trim * Units.Degree) - disp = disp.getValueAs("kg").Value * G - B.multiply(1.0 / Units.Metre.Value) + draft, + roll, + trim) + disp *= G + # Add the tanks effect on the center of gravity - cog = Vector(COG.x * W, COG.y * W, COG.z * W) + mom_x = Units.Quantity(COG.x, Units.Length) * W + mom_y = Units.Quantity(COG.y, Units.Length) * W + mom_z = Units.Quantity(COG.z, Units.Length) * W for i,t in enumerate(tanks): tank_weight = VOLS[i] * t[1] * G - cog += t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim).multiply( - tank_weight / Units.Metre.Value) - cog = cog.multiply(1.0 / (W + TW)) + tank_cog = t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim) + mom_x += Units.Quantity(tank_cog.x, Units.Length) * tank_weight + mom_y += Units.Quantity(tank_cog.y, Units.Length) * tank_weight + mom_z += Units.Quantity(tank_cog.z, Units.Length) * tank_weight + cog_x = mom_x / (W + TW) + cog_y = mom_y / (W + TW) + cog_z = mom_z / (W + TW) # Compute the errors - draft_error = -(disp - W - TW) / max_disp - R = cog - B + draft_error = -((disp - W - TW) / max_disp).Value + R_x = cog_x - Units.Quantity(B.x, Units.Length) + R_y = cog_y - Units.Quantity(B.y, Units.Length) + R_z = cog_z - Units.Quantity(B.z, Units.Length) if not var_trim: trim_error = 0.0 else: - trim_error = -TRIM_RELAX_FACTOR * R.x / ship.Length.getValueAs('m').Value + trim_error = -TRIM_RELAX_FACTOR * R_x / ship.Length # Check if we can tolerate the errors - if abs(draft_error) < 0.01 and abs(trim_error) < 0.05: + if abs(draft_error) < 0.01 and abs(trim_error) < 0.1: break # Get the new draft and trim draft += draft_error * max_draft - trim += trim_error + trim += trim_error * Units.Degree # GZ should be provided in the Free surface oriented frame of reference - c = math.cos(math.radians(roll)) - s = math.sin(math.radians(roll)) - return c * R.y - s * R.z, draft, trim \ No newline at end of file + c = math.cos(roll.getValueAs('rad')) + s = math.sin(roll.getValueAs('rad')) + return c * R_y - s * R_z, draft, trim + + +def gz(lc, rolls, var_trim=True): + """Compute the ship GZ stability curve + + Position arguments: + lc -- Load condition spreadsheet + rolls -- List of roll angles to compute + + Keyword arguments: + var_trim -- True if the equilibrium trim should be computed for each roll + angle, False if null trim angle can be used instead. + + Returned value: + List of GZ curve points. Each point contains the GZ stability length, the + equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is + False) + """ + # B1 cell must be a ship + # B2 cell must be the loading condition itself + doc = lc.Document + try: + if lc not in doc.getObjectsByLabel(lc.get('B2')): + return[] + ships = doc.getObjectsByLabel(lc.get('B1')) + if len(ships) != 1: + if len(ships) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Ship label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('B1'))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Ship label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ships), + lc.get('B1'))) + return[] + ship = ships[0] + if ship is None or not ship.PropertiesList.index("IsShip"): + return[] + except ValueError: + return[] + # Extract the weights and the tanks + weights = [] + index = 6 + while True: + try: + ws = doc.getObjectsByLabel(lc.get('A{}'.format(index))) + except ValueError: + break + index += 1 + if len(ws) != 1: + if len(ws) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Weight label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('A{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Weight label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('A{}'.format(index - 1)))) + continue + w = ws[0] + try: + if w is None or not w.PropertiesList.index("IsWeight"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Weight! (the object labeled as" + "'{}' is not a weight)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('A{}'.format(index - 1)))) + continue + except ValueError: + continue + weights.append(w) + tanks = [] + index = 6 + while True: + try: + ts = doc.getObjectsByLabel(lc.get('C{}'.format(index))) + dens = float(lc.get('D{}'.format(index))) + level = float(lc.get('E{}'.format(index))) + dens = Units.parseQuantity("{} kg/m^3".format(dens)) + except ValueError: + break + index += 1 + if len(ts) != 1: + if len(ts) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Tank label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('C{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Tank label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ts), + lc.get('C{}'.format(index - 1)))) + continue + t = ts[0] + try: + if t is None or not t.PropertiesList.index("IsTank"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Tank! (the object labeled as" + "'{}' is not a tank)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('C{}'.format(index - 1)))) + continue + except ValueError: + continue + tanks.append((t, dens, level)) + + return solve(ship, weights, tanks, rolls, var_trim) \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 7c06842d0..5460d9dd5 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -261,12 +261,12 @@ def displacement(ship, draft=None, B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) - m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0)) - m.rotateY(math.radians(trim)) + m.move(Vector(-draft * math.sin(trim.getValueAs("rad")), 0.0, 0.0)) + m.rotateY(trim.getValueAs("rad")) m.move(Vector(0.0, - -draft * math.sin(math.radians(roll)), + -draft * math.sin(roll.getValueAs("rad")), base_z)) - m.rotateX(-math.radians(roll)) + m.rotateX(-roll.getValueAs("rad")) B.transform(m) try: