#*************************************************************************** #* * #* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** import math import FreeCAD as App import FreeCADGui as Gui from FreeCAD import Vector, Matrix, Placement import Part from FreeCAD import Units import Instance as ShipInstance import WeightInstance import TankInstance from shipHydrostatics import Tools as Hydrostatics G = Units.parseQuantity("9.81 m/s^2") MAX_EQUILIBRIUM_ITERS = 10 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 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 = 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) m = w.Proxy.getMoment(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 = 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]) VOLS.append(vol) TW += vol * t[1] TW = TW * G points = [] for i,roll in enumerate(rolls): App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls))) point = solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim) if point is None: return [] points.append(point) return points def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): """ Compute the ship GZ value. @param W Empty ship weight. @param COG Empty ship Center of mass. @param TW Tanks weights. @param VOLS List of tank volumes. @param tanks Considered tanks. @param roll Roll angle. @param var_trim True if the trim angle should be recomputed at each roll angle, False otherwise. @return GZ value, equilibrium draft, and equilibrium trim angle (0 if variable trim has not been requested) """ # Look for the equilibrium draft (and eventually the trim angle too) 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) App.Console.PrintError(msg + ' ({} vs. {})\n'.format( (max_disp / G).UserString, ((W + TW) / G).UserString)) return None 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, roll, trim) disp *= G # Add the tanks effect on the center of gravity 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 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).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 # Check if we can tolerate the errors 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 * Units.Degree # GZ should be provided in the Free surface oriented frame of reference 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) App.Console.PrintError(msg + '\n'.format( lc.get('B1'))) else: msg = QtGui.QApplication.translate( "ship_console", "Ambiguous Ship label! ({} instances labeled as" "'{}' found)", None) 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) 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) 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) 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) 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) 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) 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)