diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index 4a3af6340..ca52205e7 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -29,6 +29,7 @@ SET(ShipCreateShip_SRCS shipCreateShip/Preview.py shipCreateShip/TaskPanel.py shipCreateShip/TaskPanel.ui + shipCreateShip/Tools.ui ) SOURCE_GROUP("shipcreateship" FILES ${ShipCreateShip_SRCS}) @@ -62,6 +63,7 @@ SET(ShipCreateWeight_SRCS shipCreateWeight/__init__.py shipCreateWeight/TaskPanel.py shipCreateWeight/TaskPanel.ui + shipCreateWeight/Tools.py ) SOURCE_GROUP("shipcreateweight" FILES ${ShipCreateWeight_SRCS}) @@ -69,6 +71,7 @@ SET(ShipCreateTank_SRCS shipCreateTank/__init__.py shipCreateTank/TaskPanel.py shipCreateTank/TaskPanel.ui + shipCreateTank/Tools.py ) SOURCE_GROUP("shipcreatetank" FILES ${ShipCreateTank_SRCS}) @@ -77,9 +80,25 @@ SET(ShipCapacityCurve_SRCS shipCapacityCurve/PlotAux.py shipCapacityCurve/TaskPanel.py shipCapacityCurve/TaskPanel.ui + shipCapacityCurve/Tools.py ) SOURCE_GROUP("shipcapacitycurve" FILES ${ShipCapacityCurve_SRCS}) +SET(ShipCreateLoadCondition_SRCS + shipCreateLoadCondition/__init__.py + shipCreateLoadCondition/Tools.py +) +SOURCE_GROUP("shipcreateloadcondition" FILES ${ShipCreateLoadCondition_SRCS}) + +SET(ShipGZ_SRCS + shipGZ/__init__.py + shipGZ/PlotAux.py + shipGZ/TaskPanel.py + shipGZ/TaskPanel.ui + shipGZ/Tools.py +) +SOURCE_GROUP("shipgz" FILES ${ShipGZ_SRCS}) + SET(ShipUtils_SRCS shipUtils/__init__.py shipUtils/Locale.py @@ -89,7 +108,7 @@ SET(ShipUtils_SRCS ) SOURCE_GROUP("shiputils" FILES ${ShipUtils_SRCS}) -SET(all_files ${ShipMain_SRCS} ${ShipExamples_SRCS} ${ShipLoadExample_SRCS} ${ShipCreateShip_SRCS} ${ShipOutlineDraw_SRCS} ${ShipAreasCurve_SRCS} ${ShipHydrostatics_SRCS} ${ShipCreateWeight_SRCS} ${ShipCreateTank_SRCS} ${ShipCapacityCurve_SRCS} ${ShipUtils_SRCS}) +SET(all_files ${ShipMain_SRCS} ${ShipExamples_SRCS} ${ShipLoadExample_SRCS} ${ShipCreateShip_SRCS} ${ShipOutlineDraw_SRCS} ${ShipAreasCurve_SRCS} ${ShipHydrostatics_SRCS} ${ShipCreateWeight_SRCS} ${ShipCreateTank_SRCS} ${ShipCapacityCurve_SRCS} ${ShipCreateLoadCondition_SRCS} ${ShipGZ_SRCS} ${ShipUtils_SRCS}) ADD_CUSTOM_TARGET(Ship ALL SOURCES ${all_files} ${Ship_QRC_SRCS} @@ -156,6 +175,18 @@ INSTALL( DESTINATION Mod/Ship/shipCapacityCurve ) +INSTALL( + FILES + ${ShipCreateLoadCondition_SRCS} + DESTINATION + Mod/Ship/shipCreateLoadCondition +) +INSTALL( + FILES + ${ShipGZ_SRCS} + DESTINATION + Mod/Ship/shipGZ +) INSTALL( FILES ${ShipUtils_SRCS} diff --git a/src/Mod/Ship/InitGui.py b/src/Mod/Ship/InitGui.py index 0362abc94..68cd5499f 100644 --- a/src/Mod/Ship/InitGui.py +++ b/src/Mod/Ship/InitGui.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -52,12 +52,10 @@ class ShipWorkbench(Workbench): "Ship_Hydrostatics"] weightslist = ["Ship_Weight", "Ship_Tank", - "Ship_Capacity"] - """ - weightslist = ["Ship_Weights", - "Ship_CreateTank", + "Ship_Capacity", + "Ship_LoadCondition", "Ship_GZ"] - """ + self.appendToolbar( str(QtCore.QT_TRANSLATE_NOOP("Ship", "Ship design")), shiplist) diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index 0f4b0b81e..d197f602f 100644 --- a/src/Mod/Ship/Instance.py +++ b/src/Mod/Ship/Instance.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -24,8 +24,6 @@ import time from math import * from PySide import QtGui, QtCore -from pivy.coin import * -from pivy import coin import FreeCAD import FreeCADGui from FreeCAD import Base, Vector @@ -109,6 +107,15 @@ class Ship: "Tanks", "Ship", tooltip).Tanks = [] + tooltip = str(QtGui.QApplication.translate( + "Ship", + "Set of load conditions", + None, + QtGui.QApplication.UnicodeUTF8)) + obj.addProperty("App::PropertyStringList", + "LoadConditions", + "Ship", + tooltip).LoadConditions = [] obj.Proxy = self @@ -122,6 +129,91 @@ class Ship: if prop == "Length" or prop == "Breadth" or prop == "Draft": pass + def cleanWeights(self, fp): + """Reanalyse the weights list looking for duplicated opbjects, or + removed ones. + """ + if not len(fp.Weights): + return + # Filter out the duplicated elements + filtered_list = [] + [filtered_list.append(x) for x in fp.Weights if x not in filtered_list] + if cmp(fp.Weights, filtered_list): + fp.Weights = filtered_list + # Filter out the removed/non-valid objects + object_names = [] + for obj in fp.Document.Objects: + object_names.append(obj.Name) + filtered_list = [] + for obj_name in fp.Weights: + if obj_name in object_names: + for obj in fp.Document.Objects: + if obj.Name == obj_name: + try: + if obj.IsWeight: filtered_list.append(obj_name) + except: + pass + break + if cmp(fp.Weights, filtered_list): + fp.Weights = filtered_list + + def cleanTanks(self, fp): + """Reanalyse the weights list looking for duplicated opbjects, or + removed ones. + """ + if not len(fp.Tanks): + return + # Filter out the duplicated elements + filtered_list = [] + [filtered_list.append(x) for x in fp.Tanks if x not in filtered_list] + if cmp(fp.Tanks, filtered_list): + fp.Tanks = filtered_list + # Filter out the removed/non-valid objects + object_names = [] + for obj in fp.Document.Objects: + object_names.append(obj.Name) + filtered_list = [] + for obj_name in fp.Tanks: + if obj_name in object_names: + for obj in fp.Document.Objects: + if obj.Name == obj_name: + try: + if obj.IsTank: filtered_list.append(obj_name) + except: + pass + break + if cmp(fp.Tanks, filtered_list): + fp.Tanks = filtered_list + + def cleanLoadConditions(self, fp): + """Reanalyse the weights list looking for duplicated opbjects, or + removed ones. + """ + if not len(fp.LoadConditions): + return + # Filter out the duplicated elements + filtered_list = [] + [filtered_list.append(x) for x in fp.LoadConditions if x not in filtered_list] + if cmp(fp.LoadConditions, filtered_list): + fp.LoadConditions = filtered_list + # Filter out the removed/non-valid objects + object_names = [] + for obj in fp.Document.Objects: + object_names.append(obj.Name) + filtered_list = [] + for obj_name in fp.LoadConditions: + if obj_name in object_names: + for obj in fp.Document.Objects: + if obj.Name == obj_name: + try: + if obj.TypeId == 'Spreadsheet::Sheet': + filtered_list.append(obj_name) + except: + pass + break + if cmp(fp.LoadConditions, filtered_list): + fp.LoadConditions = filtered_list + def execute(self, fp): """Detects the entity recomputations. @@ -242,6 +334,16 @@ class ViewProviderShip: del obj.Tanks[i - bad_linked] bad_linked += 1 + # Claim the loading conditions + bad_linked = 0 + for i, t in enumerate(obj.LoadConditions): + try: + t_obj = FreeCAD.ActiveDocument.getObject(t) + objs.append(t_obj) + except: + del obj.LoadConditions[i - bad_linked] + bad_linked += 1 + return objs def getIcon(self): diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py new file mode 100644 index 000000000..426d58af7 --- /dev/null +++ b/src/Mod/Ship/Ship.py @@ -0,0 +1,39 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2015 * +#* 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 * +#* * +#*************************************************************************** + + +__title__="FreeCAD Ship module" +__author__ = "Jose Luis Cercos-Pita" +__url__ = "http://www.freecadweb.org" + +__doc__="The Ships module provide a set of tools to make some specific Naval" \ + " Architecture computations" + +from shipCreateShip.Tools import createShip +from shipHydrostatics.Tools import areas, displacement, wettedArea, moment +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 +from shipGZ.Tools import gz \ No newline at end of file diff --git a/src/Mod/Ship/ShipGui.py b/src/Mod/Ship/ShipGui.py index 515daa323..9cd977624 100644 --- a/src/Mod/Ship/ShipGui.py +++ b/src/Mod/Ship/ShipGui.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -170,6 +170,40 @@ class TankCapacity: 'ToolTip': ToolTip} +class LoadCondition: + def Activated(self): + import shipCreateLoadCondition + shipCreateLoadCondition.load() + + def GetResources(self): + MenuText = QtCore.QT_TRANSLATE_NOOP( + 'ship_loadcondition', + 'Create a new loading condition') + ToolTip = QtCore.QT_TRANSLATE_NOOP( + 'ship_loadcondition', + 'Create a new load condition spreadsheet') + return {'Pixmap': 'Ship_LoadCondition', + 'MenuText': MenuText, + 'ToolTip': ToolTip} + + +class GZ: + def Activated(self): + import shipGZ + shipGZ.load() + + def GetResources(self): + MenuText = QtCore.QT_TRANSLATE_NOOP( + 'ship_gz', + 'GZ curve computation') + ToolTip = QtCore.QT_TRANSLATE_NOOP( + 'ship_gz', + 'Plot the GZ curve') + return {'Pixmap': 'Ship_GZ', + 'MenuText': MenuText, + 'ToolTip': ToolTip} + + FreeCADGui.addCommand('Ship_LoadExample', LoadExample()) FreeCADGui.addCommand('Ship_CreateShip', CreateShip()) FreeCADGui.addCommand('Ship_OutlineDraw', OutlineDraw()) @@ -178,3 +212,5 @@ FreeCADGui.addCommand('Ship_Hydrostatics', Hydrostatics()) FreeCADGui.addCommand('Ship_Weight', CreateWeight()) FreeCADGui.addCommand('Ship_Tank', CreateTank()) FreeCADGui.addCommand('Ship_Capacity', TankCapacity()) +FreeCADGui.addCommand('Ship_LoadCondition', LoadCondition()) +FreeCADGui.addCommand('Ship_GZ', GZ()) diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index 65e7ea182..053259b35 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -24,14 +24,16 @@ import time from math import * from PySide import QtGui, QtCore -from pivy.coin import * -from pivy import coin -import FreeCAD -import FreeCADGui -from FreeCAD import Base, Vector +import FreeCAD as App +import FreeCADGui as Gui +from FreeCAD import Base, Vector, Matrix, Placement, Rotation import Part import Units from shipUtils import Paths, Math +import shipUtils.Units as USys + + +COMMON_BOOLEAN_ITERATIONS = 10 class Tank: @@ -54,28 +56,6 @@ class Tank: "IsTank", "Tank", tooltip).IsTank = True - # Add the volume property (The volume of fluid will be set by each - # loading condition) - tooltip = str(QtGui.QApplication.translate( - "ship_tank", - "Volume of fluid [m^3]", - None, - QtGui.QApplication.UnicodeUTF8)) - obj.addProperty("App::PropertyFloat", - "Vol", - "Tank", - tooltip).Vol = 0.0 - # Add the density property (The volume of fluid will be set by each - # loading condition) - tooltip = str(QtGui.QApplication.translate( - "ship_tank", - "Density [kg / m^3]", - None, - QtGui.QApplication.UnicodeUTF8)) - obj.addProperty("App::PropertyFloat", - "Dens", - "Tank", - tooltip).Dens = 0.0 # Set the subshapes obj.Shape = Part.makeCompound(shapes) @@ -99,52 +79,154 @@ class Tank: """ pass - def setFillingLevel(self, fp, level): - """Compute the mass of the object, already taking into account the - type of subentities. + def getVolume(self, fp, level, return_shape=False): + """Return the fluid volume inside the tank, provided the filling level. Keyword arguments: fp -- Part::FeaturePython object affected. - level -- Percentage of filling level (from 0 to 100). + level -- Percentage of filling level (interval [0, 1]). + return_shape -- False if the tool should return the fluid volume value, + True if the tool should return the volume shape. """ - shape = fp.Shape - solids = shape.Solids + if level <= 0.0: + if return_shape: + return Part.Vertex() + return Units.Quantity(0.0, Units.Volume) + if level >= 1.0: + if return_shape: + return fp.Shape.copy() + return Units.Quantity(fp.Shape.Volume, Units.Volume) - # Get the cutting box - bbox = shape.BoundBox - z_min = bbox.ZMin - z_max = bbox.ZMax + # Build up the cutting box + bbox = fp.Shape.BoundBox dx = bbox.XMax - bbox.XMin dy = bbox.YMax - bbox.YMin - dz = level / 100.0 * (z_max - z_min) - z = z_min + dz - try: - box = Part.makeBox(3.0 * dx, - 3.0 * dy, - (z_max - z_min) + dz, - Vector(bbox.XMin - dx, - bbox.YMin - dy, - bbox.ZMin - (z_max - z_min))) - except: - fp.Vol = 0.0 - return Units.parseQuantity('0 m^3') + dz = bbox.ZMax - bbox.ZMin - # Start computing the common part of each solid component with the - # cutting box, adding the volume + box = App.ActiveDocument.addObject("Part::Box","Box") + length_format = USys.getLengthFormat() + box.Placement = Placement(Vector(bbox.XMin - dx, + bbox.YMin - dy, + bbox.ZMin - dz), + Rotation(App.Vector(0,0,1),0)) + box.Length = length_format.format(3.0 * dx) + box.Width = length_format.format(3.0 * dy) + box.Height = length_format.format((1.0 + level) * dz) + + # Create a new object on top of a copy of the tank shape + Part.show(fp.Shape.copy()) + tank = App.ActiveDocument.Objects[-1] + + # Compute the common boolean operation + App.ActiveDocument.recompute() + common = App.activeDocument().addObject("Part::MultiCommon", + "TankVolHelper") + common.Shapes = [tank, box] + App.ActiveDocument.recompute() + if len(common.Shape.Solids) == 0: + # The common operation is failing, let's try moving a bit the free + # surface + msg = QtGui.QApplication.translate( + "ship_console", + "Tank volume operation failed. The tool is retrying that" + " slightly moving the free surface position", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintWarning(msg + '\n') + rand_bounds = 0.01 * dz + i = 0 + while len(common.Shape.Solids) == 0 and i < COMMON_BOOLEAN_ITERATIONS: + i += 1 + box.Height = length_format.format( + (1.0 + level) * dz + random.uniform(-random_bounds, + random_bounds)) + App.ActiveDocument.recompute() + + if return_shape: + ret_value = common.Shape.copy() + else: + ret_value = Units.Quantity(common.Shape.Volume, Units.Volume) + + App.ActiveDocument.removeObject(common.Name) + App.ActiveDocument.removeObject(tank.Name) + App.ActiveDocument.removeObject(box.Name) + App.ActiveDocument.recompute() + + return ret_value + + 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. + + The returned center of gravity is refered to the untransformed ship. + + Keyword arguments: + fp -- Part::FeaturePython object affected. + 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 + if vol <= 0.0: + return Vector() + if vol >= fp.Shape.Volume: + vol = 0.0 + for solid in fp.Shape.Solids: + vol += solid.Volume + sCoG = solid.CenterOfMass + cog.x = cog.x + sCoG.x * solid.Volume + cog.y = cog.y + sCoG.y * solid.Volume + cog.z = cog.z + sCoG.z * solid.Volume + cog.x = cog.x / vol + cog.y = cog.y / vol + cog.z = cog.z / vol + return cog + + # Get a first estimation of the level + level = vol.Value / fp.Shape.Volume + + # Transform the tank shape + current_placement = fp.Placement + m = current_placement.toMatrix() + 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.Value - shape.Volume) / fp.Shape.Volume + if abs(error) < 0.01: + break + level += error + + # Get the center of gravity vol = 0.0 - for s in solids: - try: - fluid = s.common(box) - v = fluid.Volume - except: - v = 0.0 - vol += v + cog = Vector() + if len(shape.Solids) > 0: + for solid in shape.Solids: + vol += solid.Volume + sCoG = solid.CenterOfMass + cog.x = cog.x + sCoG.x * solid.Volume + cog.y = cog.y + sCoG.y * solid.Volume + cog.z = cog.z + sCoG.z * solid.Volume + cog.x = cog.x / vol + cog.y = cog.y / vol + cog.z = cog.z / vol - # Get the volume quantity and store it with the right units - vol = Units.Quantity(vol, Units.Volume) - fp.Vol = vol.getValueAs("m^3").Value + # Untransform the object to retrieve the original position + fp.Placement = current_placement + p = Part.Point(cog) + m = Matrix() + m.rotateY(trim.getValueAs("rad")) + m.rotateX(-roll.getValueAs("rad")) + p.rotate(Placement(m)) - return vol + return Vector(p.X, p.Y, p.Z) class ViewProviderTank: @@ -184,7 +266,7 @@ class ViewProviderTank: def getDefaultDisplayMode(self): """Return the name of the default display mode. It must be defined in getDisplayModes.""" - return "Shaded" + return "Flat Lines" def setDisplayMode(self, mode): """Map the display mode defined in attach with those defined in diff --git a/src/Mod/Ship/WeightInstance.py b/src/Mod/Ship/WeightInstance.py index 9d55d2dff..8a78c5d61 100644 --- a/src/Mod/Ship/WeightInstance.py +++ b/src/Mod/Ship/WeightInstance.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -24,8 +24,6 @@ import time from math import * from PySide import QtGui, QtCore -from pivy.coin import * -from pivy import coin import FreeCAD import FreeCADGui from FreeCAD import Base, Vector @@ -38,7 +36,7 @@ class Weight: def __init__(self, obj, shapes, ship): """ Transform a generic object to a ship instance. - Keyword arguments: + Position arguments: obj -- Part::FeaturePython created object which should be transformed in a weight instance. shapes -- Set of shapes which will compound the weight element. @@ -77,7 +75,7 @@ class Weight: # Add the area density property for surface elements tooltip = str(QtGui.QApplication.translate( "ship_weight", - "Area density [kg / m^3]", + "Area density [kg / m^2]", None, QtGui.QApplication.UnicodeUTF8)) obj.addProperty("App::PropertyFloat", @@ -102,7 +100,7 @@ class Weight: def onChanged(self, fp, prop): """Detects the ship data changes. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. prop -- Modified property name. """ @@ -112,7 +110,7 @@ class Weight: def execute(self, fp): """Detects the entity recomputations. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. """ pass @@ -120,7 +118,7 @@ class Weight: def _getPuntualMass(self, fp, shape): """Compute the mass of a puntual element. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Vertex shape object. """ @@ -129,7 +127,7 @@ class Weight: def _getLinearMass(self, fp, shape): """Compute the mass of a linear element. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Edge shape object. """ @@ -140,7 +138,7 @@ class Weight: def _getAreaMass(self, fp, shape): """Compute the mass of an area element. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Face shape object. """ @@ -151,7 +149,7 @@ class Weight: def _getVolumetricMass(self, fp, shape): """Compute the mass of a volumetric element. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Solid shape object. """ @@ -163,24 +161,27 @@ class Weight: """Compute the mass of the object, already taking into account the type of subentities. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. + + Returned value: + Object mass """ m = Units.parseQuantity('0 kg') for s in fp.Shape.Solids: - m = m + self._getVolumetricMass(fp, s) + m += self._getVolumetricMass(fp, s) for f in fp.Shape.Faces: - m = m + self._getAreaMass(fp, f) + m += self._getAreaMass(fp, f) for e in fp.Shape.Edges: - m = m + self._getLinearMass(fp, e) + m += self._getLinearMass(fp, e) for v in fp.Shape.Vertexes: - m = m + self._getPuntualMass(fp, v) + m += self._getPuntualMass(fp, v) return m def _getPuntualMoment(self, fp, shape): """Compute the moment of a puntual element (respect to 0, 0, 0). - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Vertex shape object. """ @@ -193,7 +194,7 @@ class Weight: def _getLinearMoment(self, fp, shape): """Compute the mass of a linear element (respect to 0, 0, 0). - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Edge shape object. """ @@ -207,7 +208,7 @@ class Weight: def _getAreaMoment(self, fp, shape): """Compute the mass of an area element (respect to 0, 0, 0). - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Face shape object. """ @@ -221,7 +222,7 @@ class Weight: def _getVolumetricMoment(self, fp, shape): """Compute the mass of a volumetric element (respect to 0, 0, 0). - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. shape -- Solid shape object. """ @@ -236,8 +237,11 @@ class Weight: """Compute the mass of the object, already taking into account the type of subentities. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. + + Returned value: + List of moments toward x, y and z """ m = [Units.parseQuantity('0 kg*m'), Units.parseQuantity('0 kg*m'), @@ -264,15 +268,18 @@ class Weight: """Compute the mass of the object, already taking into account the type of subentities. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. + + Returned value: + Center of Mass vector """ mass = self.getMass(fp) moment = self.getMoment(fp) cog = [] for i in range(len(moment)): cog.append(moment[i] / mass) - return cog + return Vector(cog[0].Value, cog[1].Value, cog[2].Value) class ViewProviderWeight: @@ -312,7 +319,7 @@ class ViewProviderWeight: def getDefaultDisplayMode(self): """Return the name of the default display mode. It must be defined in getDisplayModes.""" - return "Shaded" + return "Flat Lines" def setDisplayMode(self, mode): """Map the display mode defined in attach with those defined in diff --git a/src/Mod/Ship/resources/Ship.qrc b/src/Mod/Ship/resources/Ship.qrc index 9ed0d300b..f3bb5a50f 100644 --- a/src/Mod/Ship/resources/Ship.qrc +++ b/src/Mod/Ship/resources/Ship.qrc @@ -6,6 +6,7 @@ icons/Ship_GZ.svg icons/Ship_Hydrostatics.svg icons/Ship_Load.svg + icons/Ship_LoadCondition.svg icons/Ship_Logo.svg icons/Ship_Module.svg icons/Ship_OutlineDraw.svg diff --git a/src/Mod/Ship/resources/icons/Ship_LoadCondition.svg b/src/Mod/Ship/resources/icons/Ship_LoadCondition.svg new file mode 100644 index 000000000..b6f7df03d --- /dev/null +++ b/src/Mod/Ship/resources/icons/Ship_LoadCondition.svg @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TF + F + T + S + W + WNA + + + + diff --git a/src/Mod/Ship/shipAreasCurve/PlotAux.py b/src/Mod/Ship/shipAreasCurve/PlotAux.py index 4d6427a71..35c5df9a6 100644 --- a/src/Mod/Ship/shipAreasCurve/PlotAux.py +++ b/src/Mod/Ship/shipAreasCurve/PlotAux.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -89,9 +89,9 @@ class Plot(object): addInfo = ("$XCB = {0} \\; \\mathrm{{m}}$\n" "$Area_{{max}} = {1} \\; \\mathrm{{m}}^2$\n" "$\\bigtriangleup = {2} \\; \\mathrm{{tons}}$".format( - xcb, + xcb.getValueAs("m").Value, maxArea, - disp)) + disp.getValueAs("kg").Value / 1000.0)) ax.text(0.0, 0.01 * maxArea, addInfo, diff --git a/src/Mod/Ship/shipAreasCurve/Preview.py b/src/Mod/Ship/shipAreasCurve/Preview.py index a7428d5e8..44fbd6cf9 100644 --- a/src/Mod/Ship/shipAreasCurve/Preview.py +++ b/src/Mod/Ship/shipAreasCurve/Preview.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -55,11 +55,11 @@ class Preview(object): point = Base.Vector(x, y, 0.0) plane = Part.makePlane(L, B, point, Base.Vector(0, 0, 1)) plane.rotate(Base.Vector(0, 0, 0), Base.Vector(0, 1, 0), trim) - plane.translate(Base.Vector(0, 0, draft * Units.Metre.Value)) + plane.translate(Base.Vector(0, 0, draft)) Part.show(plane) objs = FreeCAD.ActiveDocument.Objects self.obj = objs[len(objs) - 1] - self.obj.Label = 'FreeSurface' + self.obj.Label = 'FreeSurfaceHelper' guiObj = FreeCADGui.ActiveDocument.getObject(self.obj.Name) guiObj.ShapeColor = (0.4, 0.8, 0.85) guiObj.Transparency = 50 @@ -69,4 +69,4 @@ class Preview(object): if not self.obj: return FreeCAD.ActiveDocument.removeObject(self.obj.Name) - self.obj = None + self.obj = None \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.py b/src/Mod/Ship/shipAreasCurve/TaskPanel.py index 787bb2dc0..c32db44f5 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.py +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -50,25 +50,25 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value - data = Hydrostatics.displacement(self.ship, - draft, - 0.0, - trim) - disp = data[0] - xcb = data[1].x + form.num = self.widget(QtGui.QSpinBox, "Num") + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) + num = form.num.value() + + disp, B, _ = Hydrostatics.displacement(self.ship, + draft, + Units.parseQuantity("0 deg"), + trim) + xcb = Units.Quantity(B.x, Units.Length) data = Hydrostatics.areas(self.ship, - draft, - 0.0, - trim) + num, + draft=draft, + trim=trim) x = [] y = [] for i in range(0, len(data)): - x.append(data[i][0]) - y.append(data[i][1]) + x.append(data[i][0].getValueAs("m").Value) + y.append(data[i][1].getValueAs("m^2").Value) PlotAux.Plot(x, y, disp, xcb, self.ship) self.preview.clean() return True @@ -104,6 +104,7 @@ class TaskPanel: form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.num = self.widget(QtGui.QSpinBox, "Num") form.output = self.widget(QtGui.QTextEdit, "OutputData") form.doc = QtGui.QTextDocument(form.output) self.form = form @@ -183,6 +184,7 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.num = self.widget(QtGui.QSpinBox, "Num") form.draft.setText(Locale.toString(length_format.format( self.ship.Draft.getValueAs(USys.getLengthUnits()).Value))) form.trim.setText(Locale.toString(angle_format.format(0.0))) @@ -202,6 +204,11 @@ class TaskPanel: USys.getAngleUnits()).Value))) except ValueError: pass + try: + props.index("AreaCurveNum") + form.num.setValue(self.ship.AreaCurveNum) + except ValueError: + pass # Update GUI draft = Units.Quantity(form.draft.text()).getValueAs('m').Value trim = Units.Quantity(form.trim.text()).getValueAs('deg').Value @@ -227,28 +234,21 @@ class TaskPanel: self.widget(QtGui.QLabel, "TrimLabel").setText( QtGui.QApplication.translate( "ship_areas", - "Trim", + "Trim angle", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "NumLabel").setText( + QtGui.QApplication.translate( + "ship_areas", + "Number of points", None, QtGui.QApplication.UnicodeUTF8)) - def clampLength(self, widget, val_min, val_max, val): - if val >= val_min and val <= val_max: + def clampValue(self, widget, val_min, val_max, val): + if val_min <= val <= val_max: return val - input_format = USys.getLengthFormat() val = min(val_max, max(val_min, val)) - qty = Units.Quantity('{} m'.format(val)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) - return val - - def clampAngle(self, widget, val_min, val_max, val): - if val >= val_min and val <= val_max: - return val - input_format = USys.getAngleFormat() - val = min(val_max, max(val_min, val)) - qty = Units.Quantity('{} deg'.format(val)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + widget.setText(val.UserString) return val def onData(self, value): @@ -265,32 +265,24 @@ class TaskPanel: # Get the values (or fix them in bad setting case) try: - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) except: - draft = self.ship.Draft.getValueAs(USys.getLengthUnits()).Value - input_format = USys.getLengthFormat() - qty = Units.Quantity('{} m'.format(draft)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + draft = self.ship.Draft + form.draft.setText(draft.UserString) try: - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) except: - trim = 0.0 - input_format = USys.getAngleFormat() - qty = Units.Quantity('{} deg'.format(trim)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + trim = Units.parseQuantity("0 deg") + form.trim.setText(trim.UserString) bbox = self.ship.Shape.BoundBox - draft_min = bbox.ZMin / Units.Metre.Value - draft_max = bbox.ZMax / Units.Metre.Value - draft = self.clampLength(form.draft, draft_min, draft_max, draft) + draft_min = Units.Quantity(bbox.ZMin, Units.Length) + draft_max = Units.Quantity(bbox.ZMax, Units.Length) + draft = self.clampValue(form.draft, draft_min, draft_max, draft) - trim_min = -180.0 - trim_max = 180.0 - trim = self.clampAngle(form.trim, trim_min, trim_max, trim) + trim_min = Units.parseQuantity("-180 deg") + trim_max = Units.parseQuantity("180 deg") + trim = self.clampValue(form.trim, trim_min, trim_max, trim) self.onUpdate() self.preview.update(draft, trim, self.ship) @@ -305,40 +297,39 @@ class TaskPanel: form.trim = self.widget(QtGui.QLineEdit, "Trim") form.output = self.widget(QtGui.QTextEdit, "OutputData") - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) # Calculate the drafts at each perpendicular - angle = math.radians(trim) + angle = trim.getValueAs("rad").Value L = self.ship.Length.getValueAs('m').Value B = self.ship.Breadth.getValueAs('m').Value - draftAP = draft + 0.5 * L * math.tan(angle) + draftAP = draft + 0.5 * self.ship.Length * math.tan(angle) if draftAP < 0.0: draftAP = 0.0 - draftFP = draft - 0.5 * L * math.tan(angle) + draftFP = draft - 0.5 * self.ship.Length * math.tan(angle) if draftFP < 0.0: draftFP = 0.0 # Calculate the involved hydrostatics - data = Hydrostatics.displacement(self.ship, - draft, - 0.0, - trim) + disp, B, _ = Hydrostatics.displacement(self.ship, + draft, + Units.parseQuantity("0 deg"), + trim) + xcb = Units.Quantity(B.x, Units.Length) # Setup the html string - string = 'L = {0} [m]
'.format(L) - string = string + 'B = {0} [m]
'.format(B) - string = string + 'T = {0} [m]
'.format(draft) - string = string + 'Trim = {0} [degrees]
'.format(trim) - string = string + 'TAP = {0} [m]
'.format(draftAP) - string = string + 'TFP = {0} [m]
'.format(draftFP) + string = u'L = {0}
'.format(self.ship.Length.UserString) + string += u'B = {0}
'.format(self.ship.Breadth.UserString) + string += u'T = {0}
'.format(draft.UserString) + string += u'Trim = {0}
'.format(trim.UserString) + string += u'TAP = {0}
'.format(draftAP.UserString) + string += u'TFP = {0}
'.format(draftFP.UserString) dispText = QtGui.QApplication.translate( "ship_areas", 'Displacement', None, QtGui.QApplication.UnicodeUTF8) - string = string + dispText + ' = {0} [ton]
'.format(data[0]) - string = string + 'XCB = {0} [m]'.format(data[1].x) + string += dispText + u' = {0}
'.format(disp.UserString) + string += u'XCB = {0}'.format(xcb.UserString) form.output.setHtml(string) def save(self): @@ -347,11 +338,11 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.num = self.widget(QtGui.QSpinBox, "Num") - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) + num = form.num.value() props = self.ship.PropertiesList try: @@ -369,7 +360,7 @@ class TaskPanel: "AreaCurveDraft", "Ship", tooltip) - self.ship.AreaCurveDraft = '{} m'.format(draft) + self.ship.AreaCurveDraft = draft try: props.index("AreaCurveTrim") except ValueError: @@ -385,7 +376,23 @@ class TaskPanel: "AreaCurveTrim", "Ship", tooltip) - self.ship.AreaCurveTrim = '{} deg'.format(trim) + self.ship.AreaCurveTrim = trim + try: + props.index("AreaCurveNum") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_areas", + "Areas curve tool number of points", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "Areas curve tool number of points" + self.ship.addProperty("App::PropertyInteger", + "AreaCurveNum", + "Ship", + tooltip) + self.ship.AreaCurveNum = num def createTask(): @@ -394,4 +401,4 @@ def createTask(): if panel.setupUi(): Gui.Control.closeDialog(panel) return None - return panel + return panel \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.ui b/src/Mod/Ship/shipAreasCurve/TaskPanel.ui index 75aa5cc81..8f1914d03 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.ui +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.ui @@ -45,7 +45,7 @@ - Trim + Trim angle @@ -55,6 +55,26 @@ + + + + Number of points + + + + + + + 2 + + + 9999 + + + 30 + + + diff --git a/src/Mod/Ship/shipAreasCurve/__init__.py b/src/Mod/Ship/shipAreasCurve/__init__.py index 812df3a4a..c8f2b6eba 100644 --- a/src/Mod/Ship/shipAreasCurve/__init__.py +++ b/src/Mod/Ship/shipAreasCurve/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipCapacityCurve/PlotAux.py b/src/Mod/Ship/shipCapacityCurve/PlotAux.py index 2b58dcab1..2dc286429 100644 --- a/src/Mod/Ship/shipCapacityCurve/PlotAux.py +++ b/src/Mod/Ship/shipCapacityCurve/PlotAux.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -27,6 +27,8 @@ import FreeCAD import FreeCADGui from FreeCAD import Base import Spreadsheet +import matplotlib.ticker as mtick + class Plot(object): def __init__(self, l, z, v, tank): @@ -64,12 +66,17 @@ class Plot(object): vols.line.set_linestyle('-') vols.line.set_linewidth(2.0) vols.line.set_color((0.0, 0.0, 0.0)) - Plot.xlabel(r'Percentage of filling level') + Plot.xlabel(r'$\mathrm{level}$') Plot.ylabel(r'$V \; [\mathrm{m}^3]$') plt.axes.xaxis.label.set_fontsize(20) plt.axes.yaxis.label.set_fontsize(20) Plot.grid(True) + # Special percentage formatter for the x axis + fmt = '%.0f%%' + xticks = mtick.FormatStrFormatter(fmt) + plt.axes.xaxis.set_major_formatter(xticks) + # Now duplicate the axes ax = Plot.addNewAxes() # Y axis can be placed at right @@ -80,20 +87,22 @@ class Plot(object): ax.yaxis.set_label_position('right') # And X axis can be placed at top ax.xaxis.tick_top() - ax.spines['top'].set_color((0.0, 0.0, 0.0)) + ax.spines['top'].set_color((0.0, 0.0, 1.0)) ax.spines['bottom'].set_color('none') ax.xaxis.set_ticks_position('top') ax.xaxis.set_label_position('top') # Plot the volume as a function of the level z coordinate - vols = Plot.plot(z, v, 'Capacity') + vols = Plot.plot(z, v, 'level') vols.line.set_linestyle('-') vols.line.set_linewidth(2.0) - vols.line.set_color((0.0, 0.0, 0.0)) + vols.line.set_color((0.0, 0.0, 1.0)) Plot.xlabel(r'$z \; [\mathrm{m}]$') Plot.ylabel(r'$V \; [\mathrm{m}^3]$') ax.xaxis.label.set_fontsize(20) ax.yaxis.label.set_fontsize(20) + ax.xaxis.label.set_color((0.0, 0.0, 1.0)) + ax.tick_params(axis='x', colors=(0.0, 0.0, 1.0)) Plot.grid(True) # End diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index 18a8c5ec4..f7cfd9ee9 100644 --- a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py +++ b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -26,6 +26,7 @@ import FreeCAD as App import FreeCADGui as Gui import Units from PySide import QtGui, QtCore +import Tools import PlotAux import TankInstance as Instance from shipUtils import Paths @@ -40,8 +41,21 @@ class TaskPanel: def accept(self): if self.tank is None: return False - # Plot data - l, z, v = self.compute() + + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.points = self.widget(QtGui.QSpinBox, "Points") + n = form.points.value() + + points = Tools.tankCapacityCurve(self.tank, n) + l = [] + z = [] + v = [] + for p in points: + l.append(p[0] * 100) + z.append(p[1].getValueAs("m").Value) + v.append(p[2].getValueAs("m^3").Value) + PlotAux.Plot(l, z, v, self.tank) return True @@ -154,29 +168,6 @@ class TaskPanel: None, QtGui.QApplication.UnicodeUTF8)) - def compute(self): - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.points = self.widget(QtGui.QSpinBox, "Points") - - bbox = self.tank.Shape.BoundBox - dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length) - - n = form.points.value() - dlevel = 100.0 / (n - 1) - l = [0.0] - v = [0.0] - z = [0.0] - - for i in range(1, n): - level = i * dlevel - vol = self.tank.Proxy.setFillingLevel(self.tank, level) - l.append(level) - z.append(level / 100.0 * dz.getValueAs("m").Value) - v.append(vol.getValueAs("m^3").Value) - return (l, z, v) - - def createTask(): panel = TaskPanel() Gui.Control.showDialog(panel) diff --git a/src/Mod/Ship/shipCapacityCurve/Tools.py b/src/Mod/Ship/shipCapacityCurve/Tools.py new file mode 100644 index 000000000..5acfae82c --- /dev/null +++ b/src/Mod/Ship/shipCapacityCurve/Tools.py @@ -0,0 +1,60 @@ +#*************************************************************************** +#* * +#* Copyright (c) 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 FreeCAD as App +import Units +import WeightInstance as Instance +import shipUtils.Units as USys +from PySide import QtGui + + +def tankCapacityCurve(tank, n): + """Create a tank capacity curve + + Position arguments: + tank -- Tank object (see createTank) + ship -- n Number of filling levels to test + + Returned value: + List of computed points. Each point contains the filling level percentage + (interval [0, 1]), the the filling level (0 for the bottom of the tank), and + the volume. + """ + bbox = tank.Shape.BoundBox + dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length) + dlevel = 1.0 / (n - 1) + out = [(0.0, Units.parseQuantity("0 m"), Units.parseQuantity("0 m^3"))] + + msg = QtGui.QApplication.translate( + "ship_console", + "Computing capacity curves", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintMessage(msg + '...\n') + for i in range(1, n): + App.Console.PrintMessage("\t{} / {}\n".format(i + 1, n)) + level = i * dlevel + vol = tank.Proxy.getVolume(tank, level) + out.append((level, level * dz, level * vol)) + return out \ No newline at end of file diff --git a/src/Mod/Ship/shipCapacityCurve/__init__.py b/src/Mod/Ship/shipCapacityCurve/__init__.py index 812df3a4a..c8f2b6eba 100644 --- a/src/Mod/Ship/shipCapacityCurve/__init__.py +++ b/src/Mod/Ship/shipCapacityCurve/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipCreateLoadCondition/Tools.py b/src/Mod/Ship/shipCreateLoadCondition/Tools.py new file mode 100644 index 000000000..adda78806 --- /dev/null +++ b/src/Mod/Ship/shipCreateLoadCondition/Tools.py @@ -0,0 +1,124 @@ +#*************************************************************************** +#* * +#* Copyright (c) 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 detaillc. * +#* * +#* 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 FreeCAD as App +import Spreadsheet +import Units + + +READ_ONLY_FOREGROUND = (0.5, 0.5, 0.5) +READ_ONLY_BACKGROUND = (0.9, 0.9, 0.9) + + +def createLoadCondition(ship): + """Create a new loading condition spreadsheet + + Position arguments: + ship -- Ship object + + Returned value: + lc -- The new loading condition spreadsheet object + + The new spreadsheet will initialize all the tanks as empty. To modify the + fluid density and the filling level percentages the columns D and E should + be edited (the first tank can be found at the row 6) + + For instance, the following code snippet can be used to set the first tank + 50% filled with water: + lc.set("D6", "998.0") + lc.set("E6", "0.5") + + The label of the tank can be extracted from the column C, for instance: + lc.get("C6") + Such information can be used to get the tank object (the followinbg code + may fail if either the tank has been removed, or several objects with the + same label already exist): + tank = App.ActiveDocument.getObjectsByLabel(lc.get("C6"))[0] + + The tool will claim the new spreadsheet loading condition as a child of the + ship object. Please do not remove the partner ship object before removing + this new loading condition before. + """ + # Create the spreadsheet + lc = App.activeDocument().addObject('Spreadsheet::Sheet', + 'LoadCondition') + + # Add a description + lc.setForeground('A1:B2', READ_ONLY_FOREGROUND) + lc.setBackground('A1:B2', READ_ONLY_BACKGROUND) + lc.setAlignment('B1:B2', 'center', 'keep') + lc.setStyle('B1:B2', 'italic', 'add') + lc.set("A1", "Ship:") + lc.set("A2", "Load condition:") + lc.set("B1", "=" + ship.Name + ".Label") + lc.set("B2", "=Label") + + # Add the weights data + lc.setAlignment('A4:A5', 'center', 'keep') + lc.setStyle('A4:A5', 'bold', 'add') + lc.setStyle('A4:A5', 'underline', 'add') + lc.set("A4", "WEIGHTS DATA") + lc.set("A5", "name") + for i in range(len(ship.Weights)): + weight = App.activeDocument().getObject(ship.Weights[i]) + lc.set("A{}".format(i + 6), "=" + weight.Name + ".Label") + lc.setForeground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_FOREGROUND) + lc.setBackground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_BACKGROUND) + + # Add the tanks data + lc.mergeCells('C4:E4') + lc.setForeground('C4:E5', READ_ONLY_FOREGROUND) + lc.setBackground('C4:E5', READ_ONLY_BACKGROUND) + lc.setAlignment('C4:E5', 'center', 'keep') + lc.setStyle('C4:E5', 'bold', 'add') + lc.setStyle('C4:E5', 'underline', 'add') + lc.set("C4", "TANKS DATA") + lc.set("C5", "name") + lc.set("D5", "Fluid density [kg/m^3]") + lc.set("E5", "Filling ratio (interval [0, 1])") + if len(ship.Tanks): + for i in range(len(ship.Tanks)): + tank = App.activeDocument().getObject(ship.Tanks[i]) + lc.set("C{}".format(i + 6), "=" + tank.Name + ".Label") + lc.set("D{}".format(i + 6), "998.0") + lc.set("E{}".format(i + 6), "0.0") + lc.setForeground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_FOREGROUND) + lc.setBackground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_BACKGROUND) + + lc.setColumnWidth('A', 128) + lc.setColumnWidth('B', 128) + lc.setColumnWidth('C', 128) + lc.setColumnWidth('D', 150) + lc.setColumnWidth('E', 200) + + # Add the spreadsheet to the list of loading conditions of the ship + lcs = ship.LoadConditions[:] + lcs.append(lc.Name) + ship.LoadConditions = lcs + ship.Proxy.cleanLoadConditions(ship) + + # Recompute to take the changes + App.activeDocument().recompute() + + return lc \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py new file mode 100644 index 000000000..7515040c5 --- /dev/null +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -0,0 +1,77 @@ +#*************************************************************************** +#* * +#* 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 FreeCAD as App +import FreeCADGui as Gui +from PySide import QtGui +import Tools + + +READ_ONLY_FOREGROUND = (0.5, 0.5, 0.5) +READ_ONLY_BACKGROUND = (0.9, 0.9, 0.9) + + +def load(): + """Directly create the load condition""" + # Check that a ship has been selected + ship = None + selObjs = Gui.Selection.getSelection() + if not selObjs: + msg = QtGui.QApplication.translate( + "ship_console", + "A ship instance must be selected before using this tool (no" + " objects selected)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + return + for i in range(len(selObjs)): + obj = selObjs[i] + props = obj.PropertiesList + try: + props.index("IsShip") + except ValueError: + continue + if obj.IsShip: + if ship: + msg = QtGui.QApplication.translate( + "ship_console", + "More than one ship have been selected (the extra" + " ships will be ignored)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintWarning(msg + '\n') + break + ship = obj + + if not ship: + msg = QtGui.QApplication.translate( + "ship_console", + "A ship instance must be selected before using this tool (no" + " valid ship found at the selected objects)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + return + + Tools.createLoadCondition(ship) \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateShip/Preview.py b/src/Mod/Ship/shipCreateShip/Preview.py index eadcecb80..d4d79e23f 100644 --- a/src/Mod/Ship/shipCreateShip/Preview.py +++ b/src/Mod/Ship/shipCreateShip/Preview.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipCreateShip/TaskPanel.py b/src/Mod/Ship/shipCreateShip/TaskPanel.py index f3084507a..fdc5bda34 100644 --- a/src/Mod/Ship/shipCreateShip/TaskPanel.py +++ b/src/Mod/Ship/shipCreateShip/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -26,6 +26,7 @@ import FreeCADGui as Gui import Units from PySide import QtGui, QtCore import Preview +import Tools import Instance from shipUtils import Paths import shipUtils.Units as USys @@ -40,19 +41,16 @@ class TaskPanel: def accept(self): """Create the ship instance""" self.preview.clean() - obj = App.ActiveDocument.addObject("Part::FeaturePython", "Ship") - ship = Instance.Ship(obj, self.solids) - Instance.ViewProviderShip(obj.ViewObject) mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.length = self.widget(QtGui.QLineEdit, "Length") form.breadth = self.widget(QtGui.QLineEdit, "Breadth") form.draft = self.widget(QtGui.QLineEdit, "Draft") - obj.Length = Locale.fromString(form.length.text()) - obj.Breadth = Locale.fromString(form.breadth.text()) - obj.Draft = Locale.fromString(form.draft.text()) - App.ActiveDocument.recompute() + Tools.createShip(self.solids, + Locale.fromString(form.length.text()), + Locale.fromString(form.breadth.text()), + Locale.fromString(form.draft.text())) return True def reject(self): diff --git a/src/Mod/Ship/shipCreateShip/Tools.py b/src/Mod/Ship/shipCreateShip/Tools.py new file mode 100644 index 000000000..58403689c --- /dev/null +++ b/src/Mod/Ship/shipCreateShip/Tools.py @@ -0,0 +1,65 @@ +#*************************************************************************** +#* * +#* Copyright (c) 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 FreeCAD as App +import Instance + + +def createShip(solids, L, B, T): + """Create a new ship instance + + Position arguments: + solids -- List of hull solid shapes + L -- Ship length between perpendiculars + B -- Ship Breadth + T -- Ship design draft + + Returned value: + The new ship object + + The solids can be easily extracted from an already existing object. For + instance, to get the solids from the selected object simply type the + following command: + + solids = Gui.ActiveDocument.ActiveObject.Object.Shape.Solids + + Regarding the Lenght, Breadth, and Draft, it is strongly recommended to use + Units.parseQuantity method, e.g. The following obfuscated code snippet build + such variables: + + import Units + L = Units.parseQuantity("25.5 m") + B = Units.parseQuantity("3.9 m") + T = Units.parseQuantity("1.0 m") + """ + obj = App.ActiveDocument.addObject("Part::FeaturePython", "Ship") + ship = Instance.Ship(obj, solids) + Instance.ViewProviderShip(obj.ViewObject) + + obj.Length = L + obj.Breadth = B + obj.Draft = T + + App.ActiveDocument.recompute() + return obj \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateShip/__init__.py b/src/Mod/Ship/shipCreateShip/__init__.py index 049140a0f..eae188603 100644 --- a/src/Mod/Ship/shipCreateShip/__init__.py +++ b/src/Mod/Ship/shipCreateShip/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipCreateTank/TaskPanel.py b/src/Mod/Ship/shipCreateTank/TaskPanel.py index b97b95655..08fb43fcb 100644 --- a/src/Mod/Ship/shipCreateTank/TaskPanel.py +++ b/src/Mod/Ship/shipCreateTank/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -25,6 +25,7 @@ import FreeCAD as App import FreeCADGui as Gui import Units from PySide import QtGui, QtCore +import Tools import TankInstance as Instance from shipUtils import Paths import shipUtils.Units as USys @@ -40,18 +41,9 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.ship = self.widget(QtGui.QComboBox, "Ship") - # Create the object ship = self.ships[form.ship.currentIndex()] - obj = App.ActiveDocument.addObject("Part::FeaturePython", "Tank") - tank = Instance.Tank(obj, self.solids, ship) - Instance.ViewProviderTank(obj.ViewObject) + Tools.createTank(self.solids, ship) - # Set it as a child of the ship - tanks = ship.Tanks[:] - tanks.append(obj.Name) - ship.Tanks = tanks - - App.ActiveDocument.recompute() return True def reject(self): diff --git a/src/Mod/Ship/shipCreateTank/Tools.py b/src/Mod/Ship/shipCreateTank/Tools.py new file mode 100644 index 000000000..bb52c9836 --- /dev/null +++ b/src/Mod/Ship/shipCreateTank/Tools.py @@ -0,0 +1,56 @@ +#*************************************************************************** +#* * +#* Copyright (c) 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 FreeCAD as App +import Units +import TankInstance as Instance + + +def createTank(solids, ship): + """Create a new tank instance + + Position arguments: + solids -- List of solid shapes + ship -- Ship owner + + Returned value: + The new tank object + + The tool will claim the new tank as a child of the ship object. Please do + not remove the partner ship object before removing this new tank before. + """ + obj = App.ActiveDocument.addObject("Part::FeaturePython", "Tank") + tank = Instance.Tank(obj, solids, ship) + Instance.ViewProviderTank(obj.ViewObject) + + # Set it as a child of the ship + tanks = ship.Tanks[:] + tanks.append(obj.Name) + ship.Tanks = tanks + ship.Proxy.cleanWeights(ship) + ship.Proxy.cleanTanks(ship) + ship.Proxy.cleanLoadConditions(ship) + + App.ActiveDocument.recompute() + return obj \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateTank/__init__.py b/src/Mod/Ship/shipCreateTank/__init__.py index 049140a0f..eae188603 100644 --- a/src/Mod/Ship/shipCreateTank/__init__.py +++ b/src/Mod/Ship/shipCreateTank/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipCreateWeight/TaskPanel.py b/src/Mod/Ship/shipCreateWeight/TaskPanel.py index 122deea52..390ce04b6 100644 --- a/src/Mod/Ship/shipCreateWeight/TaskPanel.py +++ b/src/Mod/Ship/shipCreateWeight/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -25,6 +25,7 @@ import FreeCAD as App import FreeCADGui as Gui import Units from PySide import QtGui, QtCore +import Tools import WeightInstance as Instance from shipUtils import Paths import shipUtils.Units as USys @@ -42,35 +43,10 @@ class TaskPanel: form.ship = self.widget(QtGui.QComboBox, "Ship") form.weight = self.widget(QtGui.QLineEdit, "Weight") - # Create the object ship = self.ships[form.ship.currentIndex()] - obj = App.ActiveDocument.addObject("Part::FeaturePython", "Weight") - weight = Instance.Weight(obj, self.shapes, ship) - Instance.ViewProviderWeight(obj.ViewObject) + density = Units.parseQuantity(Locale.fromString(form.weight.text())) - # Set the mass/density - m_unit = USys.getMassUnits() - l_unit = USys.getLengthUnits() - qty = Units.parseQuantity(Locale.fromString(form.weight.text())) - if self.elem_type == 1: - w_unit = m_unit - obj.Mass = qty.getValueAs(w_unit).Value - elif self.elem_type == 2: - w_unit = m_unit + '/' + l_unit - obj.LineDens = qty.getValueAs(w_unit).Value - elif self.elem_type == 3: - w_unit = m_unit + '/' + l_unit + '^2' - obj.AreaDens = qty.getValueAs(w_unit).Value - elif self.elem_type == 4: - w_unit = m_unit + '/' + l_unit + '^3' - obj.Dens = qty.getValueAs(w_unit).Value - - # Set it as a child of the ship - weights = ship.Weights[:] - weights.append(obj.Name) - ship.Weights = weights - - App.ActiveDocument.recompute() + Tools.createWeight(self.shapes, ship, density) return True def reject(self): diff --git a/src/Mod/Ship/shipCreateWeight/Tools.py b/src/Mod/Ship/shipCreateWeight/Tools.py new file mode 100644 index 000000000..112edb632 --- /dev/null +++ b/src/Mod/Ship/shipCreateWeight/Tools.py @@ -0,0 +1,91 @@ +#*************************************************************************** +#* * +#* Copyright (c) 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 FreeCAD as App +import Units +import WeightInstance as Instance +import shipUtils.Units as USys + + +def createWeight(shapes, ship, density): + """Create a new weight instance + + Position arguments: + shapes -- List of shapes of the weight + ship -- Ship owner + density -- Density of the object. 4 possibilities are considered here: + * Density as Mass/Volume: then the weight will be considered as a + volumetric object. Used for blocks or similar. + * Density as Mass/Area: then the weight will be considered as an area + element. Used for structural shells. + * Density as Mass/Length: then the weight will be cosidered as a linear + element. Used for linear structural reinforcements. + * Mass: Then a punctual mass will be considered. Used for complex + weights, like engines or other machines. + + Returned value: + The new weight object + + It is strongly recommended to pass just shapes matching with the provided + density, e.g. don't use volumetric shapes with a linear density value (kg/m) + + The tool will claim the new weight as a child of the ship object. Please do + not remove the partner ship object before removing this new weight before. + """ + # Create the object + obj = App.ActiveDocument.addObject("Part::FeaturePython", "Weight") + weight = Instance.Weight(obj, shapes, ship) + Instance.ViewProviderWeight(obj.ViewObject) + + # Setup the mass/density value + m_unit = "kg" + l_unit = "m" + m_qty = Units.Quantity(1, Units.Mass) + l_qty = Units.Quantity(1, Units.Length) + a_qty = Units.Quantity(1, Units.Area) + v_qty = Units.Quantity(1, Units.Volume) + if density.Unit == m_qty.Unit: + w_unit = m_unit + obj.Mass = density.getValueAs(w_unit).Value + elif density.Unit == (m_qty / l_qty).Unit: + w_unit = m_unit + '/' + l_unit + obj.LineDens = density.getValueAs(w_unit).Value + elif density.Unit == (m_qty / a_qty).Unit: + w_unit = m_unit + '/' + l_unit + '^2' + obj.AreaDens = density.getValueAs(w_unit).Value + elif density.Unit == (m_qty / v_qty).Unit: + w_unit = m_unit + '/' + l_unit + '^3' + obj.Dens = density.getValueAs(w_unit).Value + + # Set it as a child of the ship + weights = ship.Weights[:] + weights.append(obj.Name) + ship.Weights = weights + ship.Proxy.cleanWeights(ship) + ship.Proxy.cleanTanks(ship) + ship.Proxy.cleanLoadConditions(ship) + + App.ActiveDocument.recompute() + + return obj \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateWeight/__init__.py b/src/Mod/Ship/shipCreateWeight/__init__.py index 049140a0f..eae188603 100644 --- a/src/Mod/Ship/shipCreateWeight/__init__.py +++ b/src/Mod/Ship/shipCreateWeight/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipGZ/PlotAux.py b/src/Mod/Ship/shipGZ/PlotAux.py new file mode 100644 index 000000000..08da5e1dc --- /dev/null +++ b/src/Mod/Ship/shipGZ/PlotAux.py @@ -0,0 +1,106 @@ +#*************************************************************************** +#* * +#* 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 os +import math +from PySide import QtGui, QtCore +import FreeCAD +import FreeCADGui +import Spreadsheet +from shipUtils import Paths + + +class Plot(object): + def __init__(self, roll, gz, draft, trim): + """ Plot the GZ curve + + Position arguments: + roll -- List of roll angles (in degrees). + gz -- List of GZ values (in meters). + draft -- List of equilibrium drafts (in meters). + trim -- List of equilibrium trim angles (in degrees). + """ + self.plot(roll, gz) + self.spreadSheet(roll, gz, draft, trim) + + def plot(self, roll, gz): + """ Plot the GZ curve. + + Position arguments: + roll -- List of roll angles (in degrees). + gz -- List of GZ values (in meters). + """ + try: + import Plot + plt = Plot.figure('GZ') + except ImportError: + msg = QtGui.QApplication.translate( + "ship_console", + "Plot module is disabled, so I cannot perform the plot", + None, + QtGui.QApplication.UnicodeUTF8) + FreeCAD.Console.PrintWarning(msg + '\n') + return True + + gz_plot = Plot.plot(roll, gz, 'GZ curve') + gz_plot.line.set_linestyle('-') + gz_plot.line.set_linewidth(1.0) + gz_plot.line.set_color((0.0, 0.0, 0.0)) + + ax = Plot.axes() + Plot.xlabel(r'$\phi \; [\mathrm{deg}]$') + Plot.ylabel(r'$GZ \; [\mathrm{m}]$') + ax.xaxis.label.set_fontsize(20) + ax.yaxis.label.set_fontsize(20) + + Plot.grid(True) + plt.update() + return False + + def spreadSheet(self, roll, gz, draft, trim): + """ Create a Spreadsheet with the results + + Position arguments: + roll -- List of roll angles (in degrees). + gz -- List of GZ values (in meters). + draft -- List of equilibrium drafts (in meters). + trim -- List of equilibrium trim angles (in degrees). + """ + s = FreeCAD.activeDocument().addObject('Spreadsheet::Sheet', + 'GZ') + + # Print the header + s.set("A1", "roll [deg]") + s.set("B1", "GZ [m]") + s.set("C1", "draft [m]") + s.set("D1", "trim [deg]") + + # Print the data + for i in range(len(roll)): + s.set("A{}".format(i + 2), str(roll[i])) + s.set("B{}".format(i + 2), str(gz[i])) + s.set("C{}".format(i + 2), str(draft[i])) + s.set("D{}".format(i + 2), str(trim[i])) + + # Recompute + FreeCAD.activeDocument().recompute() \ No newline at end of file diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py new file mode 100644 index 000000000..c5dc76255 --- /dev/null +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -0,0 +1,345 @@ +#*************************************************************************** +#* * +#* 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 +import Units +from PySide import QtGui, QtCore +import PlotAux +import Tools +from shipUtils import Paths +import shipUtils.Units as USys +import shipUtils.Locale as Locale + + +class TaskPanel: + def __init__(self): + self.ui = Paths.modulePath() + "/shipGZ/TaskPanel.ui" + + def accept(self): + if self.lc is None: + return False + self.save() + + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.angle = self.widget(QtGui.QLineEdit, "Angle") + form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") + form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") + + 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)) + + 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) + + return True + + def reject(self): + return True + + def clicked(self, index): + pass + + def open(self): + pass + + def needsFullSpace(self): + return True + + def isAllowedAlterSelection(self): + return False + + def isAllowedAlterView(self): + return True + + def isAllowedAlterDocument(self): + return False + + def helpRequested(self): + pass + + def setupUi(self): + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + + form.angle = self.widget(QtGui.QLineEdit, "Angle") + form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") + form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") + self.form = form + if self.initValues(): + return True + self.retranslateUi() + + def getMainWindow(self): + toplevel = QtGui.qApp.topLevelWidgets() + for i in toplevel: + if i.metaObject().className() == "Gui::MainWindow": + return i + raise RuntimeError("No main window found") + + def widget(self, class_id, name): + """Return the selected widget. + + Keyword arguments: + class_id -- Class identifier + name -- Name of the widget + """ + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + return form.findChild(class_id, name) + + def initValues(self): + """ Set initial values for fields + """ + # Look for selected loading conditions (Spreadsheets) + self.lc = None + selObjs = Gui.Selection.getSelection() + if not selObjs: + msg = QtGui.QApplication.translate( + "ship_console", + "A loading condition instance must be selected before using" + " this tool (no objects selected)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + return True + for i in range(len(selObjs)): + obj = selObjs[i] + try: + if obj.TypeId != 'Spreadsheet::Sheet': + continue + except ValueError: + continue + # Check if it is a Loading condition: + # B1 cell must be a ship + # B2 cell must be the loading condition itself + doc = App.ActiveDocument + try: + if obj not in doc.getObjectsByLabel(obj.get('B2')): + continue + ships = doc.getObjectsByLabel(obj.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( + obj.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), + obj.get('B1'))) + continue + ship = ships[0] + if ship is None or not ship.PropertiesList.index("IsShip"): + continue + except ValueError: + continue + # Let's see if several loading conditions have been selected (and + # prompt a warning) + if self.lc: + msg = QtGui.QApplication.translate( + "ship_console", + "More than one loading condition have been selected (the" + " extra loading conditions will be ignored)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintWarning(msg + '\n') + break + self.lc = obj + self.ship = ship + if not self.lc: + msg = QtGui.QApplication.translate( + "ship_console", + "A loading condition instance must be selected before using" + " this tool (no valid loading condition found at the selected" + " objects)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + return True + + # We have a valid loading condition, let's set the initial field values + angle_format = USys.getAngleFormat() + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.angle = self.widget(QtGui.QLineEdit, "Angle") + form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") + form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") + form.angle.setText(Locale.toString(angle_format.format(90.0))) + # Try to use saved values + props = self.ship.PropertiesList + try: + props.index("GZAngle") + form.angle.setText(Locale.toString(angle_format.format( + self.ship.GZAngle.getValueAs( + USys.getAngleUnits()).Value))) + except: + pass + try: + props.index("GZNumPoints") + form.n_points.setValue(self.ship.GZNumPoints) + except ValueError: + pass + try: + props.index("GZVariableTrim") + if self.ship.GZVariableTrim: + form.var_trim.setCheckState(QtCore.Qt.Checked) + else: + form.var_trim.setCheckState(QtCore.Qt.Unchecked) + except ValueError: + pass + + + return False + + def retranslateUi(self): + """ Set user interface locale strings. """ + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.setWindowTitle(QtGui.QApplication.translate( + "ship_gz", + "Plot the GZ curve", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "AngleLabel").setText( + QtGui.QApplication.translate( + "ship_gz", + "Maximum angle", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "NumPointsLabel").setText( + QtGui.QApplication.translate( + "ship_gz", + "Number of points", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QCheckBox, "VariableTrim").setText( + QtGui.QApplication.translate( + "ship_gz", + "Variable trim", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QCheckBox, "VariableTrim").setToolTip( + QtGui.QApplication.translate( + "ship_gz", + "The ship will be rotated to the equilibrium trim angle for" + \ + " each roll angle. It will significantly increase the" + \ + " required computing time", + None, + QtGui.QApplication.UnicodeUTF8)) + + def save(self): + """ Saves the data into ship instance. """ + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.angle = self.widget(QtGui.QLineEdit, "Angle") + form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") + form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") + + angle = Units.Quantity(Locale.fromString( + form.angle.text())).getValueAs('deg').Value + n_points = form.n_points.value() + var_trim = form.var_trim.isChecked() + + props = self.ship.PropertiesList + try: + props.index("GZAngle") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_gz", + "GZ curve tool angle selected [deg]", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "GZ curve tool angle selected [deg]" + self.ship.addProperty("App::PropertyAngle", + "GZAngle", + "Ship", + tooltip) + self.ship.GZAngle = '{} deg'.format(angle) + try: + props.index("GZNumPoints") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_gz", + "GZ curve tool number of points selected", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "GZ curve tool number of points selected" + self.ship.addProperty("App::PropertyInteger", + "GZNumPoints", + "Ship", + tooltip) + self.ship.GZNumPoints = n_points + try: + props.index("GZVariableTrim") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_gz", + "GZ curve tool variable trim angle selection", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "GZ curve tool variable trim angle selection" + self.ship.addProperty("App::PropertyBool", + "GZVariableTrim", + "Ship", + tooltip) + self.ship.GZVariableTrim = var_trim + +def createTask(): + panel = TaskPanel() + Gui.Control.showDialog(panel) + if panel.setupUi(): + Gui.Control.closeDialog(panel) + return None + return panel \ No newline at end of file diff --git a/src/Mod/Ship/shipGZ/TaskPanel.ui b/src/Mod/Ship/shipGZ/TaskPanel.ui new file mode 100644 index 000000000..b57157e5f --- /dev/null +++ b/src/Mod/Ship/shipGZ/TaskPanel.ui @@ -0,0 +1,75 @@ + + + TaskPanel + + + + 0 + 0 + 260 + 256 + + + + GZ stability curve + + + + + + + + Number of points + + + + + + + 2 + + + 11 + + + + + + + + 0 + 0 + + + + Maximum angle + + + + + + + + + + Variable Trim angle + + + true + + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py new file mode 100644 index 000000000..c3bad14b2 --- /dev/null +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -0,0 +1,316 @@ +#*************************************************************************** +#* * +#* 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 +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, + QtGui.QApplication.UnicodeUTF8) + 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, + 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/shipGZ/__init__.py b/src/Mod/Ship/shipGZ/__init__.py new file mode 100644 index 000000000..c8f2b6eba --- /dev/null +++ b/src/Mod/Ship/shipGZ/__init__.py @@ -0,0 +1,29 @@ +#*************************************************************************** +#* * +#* 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 TaskPanel + + +def load(): + """ Loads the tool """ + TaskPanel.createTask() diff --git a/src/Mod/Ship/shipHydrostatics/PlotAux.py b/src/Mod/Ship/shipHydrostatics/PlotAux.py index c911b1822..51702c1c4 100644 --- a/src/Mod/Ship/shipHydrostatics/PlotAux.py +++ b/src/Mod/Ship/shipHydrostatics/PlotAux.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -84,11 +84,11 @@ class Plot(object): t1cm = [] xcb = [] for i in range(len(self.points)): - disp.append(self.points[i].disp) - draft.append(self.points[i].draft) - warea.append(self.points[i].wet) - t1cm.append(self.points[i].mom) - xcb.append(self.points[i].xcb) + disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0) + draft.append(self.points[i].draft.getValueAs("m").Value) + warea.append(self.points[i].wet.getValueAs("m^2").Value) + t1cm.append(self.points[i].mom.getValueAs("kg*m").Value / 1000.0) + xcb.append(self.points[i].xcb.getValueAs("m").Value) axes = Plot.axesList() for ax in axes: @@ -166,11 +166,11 @@ class Plot(object): kbt = [] bmt = [] for i in range(len(self.points)): - disp.append(self.points[i].disp) - draft.append(self.points[i].draft) - farea.append(self.points[i].farea) - kbt.append(self.points[i].KBt) - bmt.append(self.points[i].BMt) + disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0) + draft.append(self.points[i].draft.getValueAs("m").Value) + farea.append(self.points[i].farea.getValueAs("m^2").Value) + kbt.append(self.points[i].KBt.getValueAs("m").Value) + bmt.append(self.points[i].BMt.getValueAs("m").Value) axes = Plot.axesList() for ax in axes: @@ -248,8 +248,8 @@ class Plot(object): cf = [] cm = [] for i in range(len(self.points)): - disp.append(self.points[i].disp) - draft.append(self.points[i].draft) + disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0) + draft.append(self.points[i].draft.getValueAs("m").Value) cb.append(self.points[i].Cb) cf.append(self.points[i].Cf) cm.append(self.points[i].Cm) @@ -322,17 +322,28 @@ class Plot(object): # Print the data for i in range(len(self.points)): point = self.points[i] - s.set("A{}".format(i + 2), str(point.disp)) - s.set("B{}".format(i + 2), str(point.draft)) - s.set("C{}".format(i + 2), str(point.wet)) - s.set("D{}".format(i + 2), str(point.mom)) - s.set("E{}".format(i + 2), str(point.farea)) - s.set("F{}".format(i + 2), str(point.xcb)) - s.set("G{}".format(i + 2), str(point.KBt)) - s.set("H{}".format(i + 2), str(point.BMt)) - s.set("I{}".format(i + 2), str(point.Cb)) - s.set("J{}".format(i + 2), str(point.Cf)) - s.set("K{}".format(i + 2), str(point.Cm)) + s.set("A{}".format(i + 2), + str(point.disp.getValueAs("kg").Value / 1000.0)) + s.set("B{}".format(i + 2), + str(point.draft.getValueAs("m").Value)) + s.set("C{}".format(i + 2), + str(point.wet.getValueAs("m^2").Value)) + s.set("D{}".format(i + 2), + str(point.mom.getValueAs("kg*m").Value / 1000.0)) + s.set("E{}".format(i + 2), + str(point.farea.getValueAs("m^2").Value)) + s.set("F{}".format(i + 2), + str(point.xcb.getValueAs("m").Value)) + s.set("G{}".format(i + 2), + str(point.KBt.getValueAs("m").Value)) + s.set("H{}".format(i + 2), + str(point.BMt.getValueAs("m").Value)) + s.set("I{}".format(i + 2), + str(point.Cb)) + s.set("J{}".format(i + 2), + str(point.Cf)) + s.set("K{}".format(i + 2), + str(point.Cm)) # Recompute FreeCAD.activeDocument().recompute() \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/TaskPanel.py b/src/Mod/Ship/shipHydrostatics/TaskPanel.py index 100a9278d..c91f40912 100644 --- a/src/Mod/Ship/shipHydrostatics/TaskPanel.py +++ b/src/Mod/Ship/shipHydrostatics/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -57,12 +57,9 @@ class TaskPanel: form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value - min_draft = Units.Quantity(Locale.fromString( - form.minDraft.text())).getValueAs('m').Value - max_draft = Units.Quantity(Locale.fromString( - form.maxDraft.text())).getValueAs('m').Value + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) + min_draft = Units.parseQuantity(Locale.fromString(form.minDraft.text())) + max_draft = Units.parseQuantity(Locale.fromString(form.maxDraft.text())) n_draft = form.nDraft.value() draft = min_draft @@ -72,7 +69,6 @@ class TaskPanel: draft = draft + dDraft drafts.append(draft) - # Compute data # Get external faces self.loop = QtCore.QEventLoop() self.timer = QtCore.QTimer() @@ -94,6 +90,7 @@ class TaskPanel: App.Console.PrintError(msg + '\n') return False faces = Part.makeShell(faces) + # Get the hydrostatics msg = QtGui.QApplication.translate( "ship_console", diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 6f07aead4..5460d9dd5 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -22,45 +22,166 @@ #*************************************************************************** import math -from FreeCAD import Vector +import random +from FreeCAD import Vector, Rotation, Matrix, Placement import Part import Units import FreeCAD as App import FreeCADGui as Gui +from PySide import QtGui, QtCore import Instance from shipUtils import Math +import shipUtils.Units as USys -def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): - """ Compute the ship transversal areas. - @param ship Ship instance. - @param draft Ship draft. - @param roll Ship roll angle. - @param trim Ship trim angle. - @param yaw Ship yaw angle. Ussually you don't want to use this - value. - @param n Number of sections to perform. - @return Transversal areas (every area value is composed by x - coordinate and computed area) +DENS = Units.parseQuantity("1025 kg/m^3") # Salt water +COMMON_BOOLEAN_ITERATIONS = 10 + + +def placeShipShape(shape, draft, roll, trim): + """Move the ship shape such that the free surface matches with the plane + z=0. The transformation will be applied on the input shape, so copy it + before calling this method if it should be preserved. + + Position arguments: + shape -- Ship shape + draft -- Ship draft + roll -- Roll angle + trim -- Trim angle + + Returned values: + shape -- The same transformed input shape. Just for debugging purposes, you + can discard it. + base_z -- The new base z coordinate (after applying the roll angle). Useful + if you want to revert back the transformation + """ + # Roll the ship. In order to can deal with large roll angles, we are + # proceeding as follows: + # 1.- Applying the roll with respect the base line + # 2.- Recentering the ship in the y direction + # 3.- Readjusting the base line + shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) + base_z = shape.BoundBox.ZMin + shape.translate(Vector(0.0, draft * math.sin(math.radians(roll)), -base_z)) + # Trim the ship. In this case we only need to correct the x direction + shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) + shape.translate(Vector(draft * math.sin(math.radians(trim)), 0.0, 0.0)) + shape.translate(Vector(0.0, 0.0, -draft)) + + return shape, base_z + + +def getUnderwaterSide(shape, force=True): + """Get the underwater shape, simply cropping the provided shape by the z=0 + free surface plane. + + Position arguments: + shape -- Solid shape to be cropped + + Keyword arguments: + force -- True if in case the common boolean operation fails, i.e. returns + no solids, the tool should retry it slightly moving the free surface. False + otherwise. (True by default) + + Returned value: + Cropped shape. It is not modifying the input shape + """ + # Convert the shape into an active object + Part.show(shape) + orig = App.ActiveDocument.Objects[-1] + + bbox = shape.BoundBox + xmin = bbox.XMin + xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax + zmin = bbox.ZMin + zmax = bbox.ZMax + + # Create the "sea" box to intersect the ship + L = xmax - xmin + B = ymax - ymin + H = zmax - zmin + + box = App.ActiveDocument.addObject("Part::Box","Box") + length_format = USys.getLengthFormat() + box.Placement = Placement(Vector(xmin - L, ymin - B, zmin - H), + Rotation(App.Vector(0,0,1),0)) + box.Length = length_format.format(3.0 * L) + box.Width = length_format.format(3.0 * B) + box.Height = length_format.format(- zmin + H) + + App.ActiveDocument.recompute() + common = App.activeDocument().addObject("Part::MultiCommon", + "UnderwaterSideHelper") + common.Shapes = [orig, box] + App.ActiveDocument.recompute() + if force and len(common.Shape.Solids) == 0: + # The common operation is failing, let's try moving a bit the free + # surface + msg = QtGui.QApplication.translate( + "ship_console", + "Boolean operation failed when trying to get the underwater side." + " The tool is retrying such operation slightly moving the free" + " surface position", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintWarning(msg + '\n') + random_bounds = 0.01 * H + i = 0 + while len(common.Shape.Solids) == 0 and i < COMMON_BOOLEAN_ITERATIONS: + i += 1 + box.Height = length_format.format( + - zmin + H + random.uniform(-random_bounds, random_bounds)) + App.ActiveDocument.recompute() + + out = common.Shape + App.ActiveDocument.removeObject(common.Name) + App.ActiveDocument.removeObject(orig.Name) + App.ActiveDocument.removeObject(box.Name) + App.ActiveDocument.recompute() + return out + + +def areas(ship, n, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship transversal areas + + Position arguments: + ship -- Ship object (see createShip) + n -- Number of points to compute + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + List of sections, each section contains 2 values, the x longitudinal + coordinate, and the transversal area. If n < 2, an empty list will be + returned. """ if n < 2: return [] - # We will take a duplicate of ship shape in order to conviniently - # manipulate it - shape = ship.Shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) + + if draft is None: + draft = ship.Draft + + shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) + # Sections distance computation bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax dx = (xmax - xmin) / (n - 1.0) + # Since we are computing the sections in the total length (not in the # length between perpendiculars), we can grant that the starting and # ending sections have null area - areas = [[xmin / Units.Metre.Value, 0.0]] + areas = [(Units.Quantity(xmin, Units.Length), + Units.Quantity(0.0, Units.Area))] # And since we just need to compute areas we will create boxes with its # front face at the desired transversal area position, computing the # common solid part, dividing it by faces, and getting only the desired @@ -71,396 +192,350 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): for i in range(1, n - 1): App.Console.PrintMessage("{0} / {1}\n".format(i, n - 2)) x = xmin + i * dx - area = 0.0 - # Create the box - L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin) try: - box = Part.makeBox(1.5 * L + x, 3.0 * B, -bbox.ZMin, p) + f = Part.Face(shape.slice(Vector(1,0,0), x)) except Part.OCCError: - areas.append([x, area]) + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Transversal area computation failed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + areas.append((Units.Quantity(x, Units.Length), + Units.Quantity(0.0, Units.Area))) continue - # Compute the common part with ship - for s in shape.Solids: - try: - common = box.common(s) - except Part.OCCError: - continue - if common.Volume == 0.0: - continue - # Recompute object adding it to the scene, when we have - # computed desired data we can remove it. - try: - Part.show(common) - except App.Base.FreeCADError: - continue - # Divide the solid by faces and compute only the well placed ones - faces = common.Faces - for f in faces: - faceBounds = f.BoundBox - # Orientation filter - if faceBounds.XMax - faceBounds.XMin > 0.00001: - continue - # Place filter - if abs(faceBounds.XMax - x) > 0.00001: - continue - # It is a valid face, so we can add this area - area = area + f.Area / Units.Metre.Value**2 - # Destroy the last generated object - App.ActiveDocument.removeObject( - App.ActiveDocument.Objects[-1].Name) - areas.append([x / Units.Metre.Value, area]) + # It is a valid face, so we can add this area + areas.append((Units.Quantity(x, Units.Length), + Units.Quantity(f.Area, Units.Area))) # Last area is equal to zero (due to the total length usage) - areas.append([xmax / Units.Metre.Value, 0.0]) + areas.append((Units.Quantity(xmax, Units.Length), + Units.Quantity(0.0, Units.Area))) App.Console.PrintMessage("Done!\n") return areas -def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): - """ Compute the ship displacement. - @param ship Ship instance. - @param draft Ship draft. - @param roll Ship roll angle. - @param trim Ship trim angle. - @param yaw Ship yaw angle. Ussually you don't want to use this - value. - @return [disp, B, Cb], \n - - disp = Ship displacement [ton]. - - B = Bouyance center [m]. - - Cb = Block coefficient. - @note Bouyance center will returned as a FreeCAD.Vector instance. - @note Returned Bouyance center is in the non modified ship coordinates +def displacement(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship displacement + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned values: + disp -- The ship displacement (a density of the water of 1025 kg/m^3 is + assumed) + B -- Bouyance application point, i.e. Center of mass of the underwater side + Cb -- Block coefficient + + The Bouyance center is refered to the original ship position. """ - # We will take a duplicate of ship shape in order to conviniently - # manipulate it - shape = ship.Shape.copy() + if draft is None: + draft = ship.Draft - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) - - bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - # Create the "sea" box to intersect the ship - L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0) - try: - box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return [0.0, Vector(), 0.0] + shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() - for solid in shape.Solids: - # Compute the common part of the "sea" with the ship - try: - common = box.common(solid) - except Part.OCCError: - continue - # Get the data - vol = vol + common.Volume / Units.Metre.Value**3 - for s in common.Solids: - sCoG = s.CenterOfMass - cog.x = cog.x + sCoG.x * s.Volume / Units.Metre.Value**4 - cog.y = cog.y + sCoG.y * s.Volume / Units.Metre.Value**4 - cog.z = cog.z + sCoG.z * s.Volume / Units.Metre.Value**4 - cog.x = cog.x / vol - cog.y = cog.y / vol - cog.z = cog.z / vol - Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3 - # Undo the transformations - B = Vector() - B.x = cog.x * math.cos(math.radians(-yaw)) - \ - cog.y * math.sin(math.radians(-yaw)) - B.y = cog.x * math.sin(math.radians(-yaw)) + \ - cog.y * math.cos(math.radians(-yaw)) - B.z = cog.z - cog.x = B.x * math.cos(math.radians(-trim)) - \ - B.z * math.sin(math.radians(-trim)) - cog.y = B.y - cog.z = B.x * math.sin(math.radians(-trim)) + \ - B.z * math.cos(math.radians(-trim)) - B.x = cog.x - B.y = cog.y * math.cos(math.radians(-roll)) - \ - cog.z * math.sin(math.radians(-roll)) - B.z = cog.y * math.sin(math.radians(-roll)) + \ - cog.z * math.cos(math.radians(-roll)) - B.z = B.z + draft - # Return the computed data - dens = 1.025 # [tons/m3], salt water - return [dens*vol, B, vol/Vol] - - -def wettedArea(shape, draft, trim): - """ Calculate wetted ship area. - @param shape Ship external faces instance. - @param draft Draft. - @param trim Trim in degrees. - @return Wetted ship area. - """ - area = 0.0 - nObjects = 0 - - shape = shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) + if len(shape.Solids) > 0: + for solid in shape.Solids: + vol += solid.Volume + sCoG = solid.CenterOfMass + cog.x = cog.x + sCoG.x * solid.Volume + cog.y = cog.y + sCoG.y * solid.Volume + cog.z = cog.z + sCoG.z * solid.Volume + cog.x = cog.x / vol + cog.y = cog.y / vol + cog.z = cog.z / vol bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax + Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) + + # Undo the transformations on the bouyance point + 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(trim.getValueAs("rad")), 0.0, 0.0)) + m.rotateY(trim.getValueAs("rad")) + m.move(Vector(0.0, + -draft * math.sin(roll.getValueAs("rad")), + base_z)) + m.rotateX(-roll.getValueAs("rad")) + B.transform(m) - # Create the "sea" box - L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) try: - box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return 0.0 - - for f in shape.Faces: - try: - common = box.common(f) - except Part.OCCError: - continue - area = area + common.Area - return area / Units.Metre.Value**2 + cb = vol / Vol + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null volume found during the displacement" + " computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cb = 0.0 -def moment(ship, draft, trim, disp, xcb): - """ Calculate triming 1cm ship moment. - @param ship Selected ship instance - @param draft Draft. - @param trim Trim in degrees. - @param disp Displacement at selected draft and trim. - @param xcb Bouyance center at selected draft and trim. - @return Moment to trim ship 1cm (ton m). - @note Moment is positive when produce positive trim. + # Return the computed data + return (DENS * Units.Quantity(vol, Units.Volume), + Vector(B.X, B.Y, B.Z), + cb) + + +def wettedArea(shape, draft, roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship wetted area + + Position arguments: + shape -- External faces of the ship hull + draft -- Ship draft + + Keyword arguments: + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + The wetted area, i.e. The underwater side area """ + shape, _ = placeShipShape(shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape, force=False) + + area = 0.0 + for f in shape.Faces: + area = area + f.Area + return Units.Quantity(area, Units.Area) + + +def moment(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the moment required to trim the ship 1cm + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + Moment required to trim the ship 1cm. Such moment is positive if it cause a + positive trim angle. The moment is expressed as a mass by a distance, not as + a force by a distance + """ + disp_orig, B_orig, _ = displacement(ship, draft, roll, trim) + xcb_orig = Units.Quantity(B_orig.x, Units.Length) + factor = 10.0 - angle = factor * math.degrees(math.atan2( - 0.01, - 0.5 * ship.Length.getValueAs('m').Value)) - newTrim = trim + angle - data = displacement(ship, draft, 0.0, newTrim, 0.0) - mom0 = -disp * xcb - mom1 = -data[0] * data[1].x + x = 0.5 * ship.Length.getValueAs('cm').Value + y = 1.0 + angle = math.atan2(y, x) * Units.Radian + trim_new = trim + factor * angle + disp_new, B_new, _ = displacement(ship, draft, roll, trim_new) + xcb_new = Units.Quantity(B_new.x, Units.Length) + + mom0 = -disp_orig * xcb_orig + mom1 = -disp_new * xcb_new return (mom1 - mom0) / factor -def FloatingArea(ship, draft, trim): - """ Calculate ship floating area. - @param ship Selected ship instance - @param draft Draft. - @param trim Trim in degrees. - @return Ship floating area, and floating coefficient. - """ - area = 0.0 - cf = 0.0 - maxX = 0.0 - minX = 0.0 - maxY = 0.0 - minY = 0.0 +def floatingArea(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship floating area - shape = ship.Shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned values: + area -- Ship floating area + cf -- Floating area coefficient + """ + if draft is None: + draft = ship.Draft + + # We wanna intersect the whole ship with the free surface, so in this case + # we must not use the underwater side (or the tool will fail) + shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) + + try: + f = Part.Face(shape.slice(Vector(0,0,1), 0.0)) + area = Units.Quantity(f.Area, Units.Area) + except Part.OCCError: + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Floating area cannot be computed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + area = Units.Quantity(0.0, Units.Area) bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - - # Create the "sea" box - L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) + Area = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) try: - box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return [area, cf] + cf = area.Value / Area + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null area found during the floating area" + " computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cf = 0.0 - maxX = bbox.XMin / Units.Metre.Value - minX = bbox.XMax / Units.Metre.Value - maxY = bbox.YMin / Units.Metre.Value - minY = bbox.YMax / Units.Metre.Value - for s in shape.Solids: - try: - common = box.common(s) - except Part.OCCError: - continue - if common.Volume == 0.0: - continue - # Recompute the object adding it to the scene. OpenCASCADE must be - # performing an internal tesellation doing that - try: - Part.show(common) - except (TypeError,Part.OCCError): - continue - # Divide the solid by faces and filter the well placed ones - faces = common.Faces - for f in faces: - faceBounds = f.BoundBox - # Orientation filter - if faceBounds.ZMax - faceBounds.ZMin > 0.00001: - continue - # Position filter - if abs(faceBounds.ZMax) > 0.00001: - continue - - area = area + f.Area / Units.Metre.Value**2 - maxX = max(maxX, faceBounds.XMax / Units.Metre.Value) - minX = min(minX, faceBounds.XMin / Units.Metre.Value) - maxY = max(maxY, faceBounds.YMax / Units.Metre.Value) - minY = min(minY, faceBounds.YMin / Units.Metre.Value) - App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name) - - dx = maxX - minX - dy = maxY - minY - if dx*dy > 0.0: - cf = area / (dx * dy) - return [area, cf] + return area, cf -def BMT(ship, draft, trim=0.0): - """ Calculate ship Bouyance center transversal distance. - @param ship Ship instance. - @param draft Ship draft. - @param trim Ship trim angle. - @return BM Bouyance to metacenter height [m]. +def BMT(ship, draft=None, trim=Units.parseQuantity("0 deg")): + """Calculate "ship Bouyance center" - "transversal metacenter" radius + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + BMT radius """ + if draft is None: + draft = ship.Draft + + roll = Units.parseQuantity("0 deg") + _, B0, _ = displacement(ship, draft, roll, trim) + + nRoll = 2 - maxRoll = 7.0 - B0 = displacement(ship, draft, 0.0, trim, 0.0)[1] + maxRoll = Units.parseQuantity("7 deg") + BM = 0.0 for i in range(nRoll): - roll = (maxRoll / nRoll)*(i + 1) - B1 = displacement(ship, draft, roll, trim, 0.0)[1] + roll = (maxRoll / nRoll) * (i + 1) + _, B1, _ = displacement(ship, draft, roll, trim) # * M # / \ # / \ BM ==|> BM = (BB/2) / sin(alpha/2) # / \ # *-------* # BB - BB = [B1.y - B0.y, B1.z - B0.z] - BB = math.sqrt(BB[0] * BB[0] + BB[1] * BB[1]) - # nRoll is acting as the weight function - BM = BM + 0.5 * BB / math.sin(math.radians(0.5 * roll)) / nRoll - return BM + BB = B1 - B0 + BB.x = 0.0 + # nRoll is actually representing the weight function + BM += 0.5 * BB.Length / math.sin(math.radians(0.5 * roll)) / nRoll + return Units.Quantity(BM, Units.Length) -def mainFrameCoeff(ship, draft): - """ Calculate main frame coefficient. - @param ship Selected ship instance - @param draft Draft. - @return Main frame coefficient +def mainFrameCoeff(ship, draft=None): + """Compute the main frame coefficient + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + + Returned value: + Ship main frame area coefficient """ - cm = 0.0 - maxY = 0.0 - minY = 0.0 + if draft is None: + draft = ship.Draft - shape = ship.Shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - x = 0.0 - area = 0.0 + shape, _ = placeShipShape(ship.Shape.copy(), draft, + Units.parseQuantity("0 deg"), + Units.parseQuantity("0 deg")) + shape = getUnderwaterSide(shape) + + try: + f = Part.Face(shape.slice(Vector(1,0,0), 0.0)) + area = f.Area + except Part.OCCError: + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Main frame area cannot be computed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + area = 0.0 bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax + Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin) - # Create the "sea" box - L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) try: - box = Part.makeBox(1.5 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return cm + cm = area / Area + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null area found during the main frame area" + " coefficient computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cm = 0.0 - maxY = bbox.YMin / Units.Metre.Value - minY = bbox.YMax / Units.Metre.Value - for s in shape.Solids: - try: - common = box.common(s) - except Part.OCCError: - continue - if common.Volume == 0.0: - continue - # Recompute the object adding it to the scene. OpenCASCADE must be - # performing an internal tesellation doing that - try: - Part.show(common) - except (TypeError,Part.OCCError): - continue - # Divide the solid by faces and filter the well placed ones - faces = common.Faces - for f in faces: - faceBounds = f.BoundBox - # Orientation filter - if faceBounds.XMax - faceBounds.XMin > 0.00001: - continue - # Position filter - if abs(faceBounds.XMax - x) > 0.00001: - continue - - area = area + f.Area / Units.Metre.Value**2 - maxY = max(maxY, faceBounds.YMax / Units.Metre.Value) - minY = min(minY, faceBounds.YMin / Units.Metre.Value) - App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name) - - dy = maxY - minY - if dy * draft > 0.0: - cm = area / (dy * draft) return cm class Point: - """ Hydrostatics point, that conatins: \n - draft Ship draft [m]. \n - trim Ship trim [deg]. \n - disp Ship displacement [ton]. \n - xcb Bouyance center X coordinate [m]. - wet Wetted ship area [m2]. - mom Triming 1cm ship moment [ton m]. - farea Floating area [m2]. - KBt Transversal KB height [m]. - BMt Transversal BM height [m]. - Cb Block coefficient. - Cf Floating coefficient. - Cm Main frame coefficient. - @note Moment is positive when produce positive trim. + """Hydrostatics point, that contains the following members: + + draft -- Ship draft + trim -- Ship trim + disp -- Ship displacement + xcb -- Bouyance center X coordinate + wet -- Wetted ship area + mom -- Triming 1cm ship moment + farea -- Floating area + KBt -- Transversal KB height + BMt -- Transversal BM height + Cb -- Block coefficient. + Cf -- Floating coefficient. + Cm -- Main frame coefficient. + + The moment to trim the ship 1 cm is positive when is resulting in a positive + trim angle. """ def __init__(self, ship, faces, draft, trim): - """ Use all hydrostatics tools to define a hydrostatics - point. - @param ship Selected ship instance - @param faces Ship external faces - @param draft Draft. - @param trim Trim in degrees. + """Compute all the hydrostatics. + + Position argument: + ship -- Ship instance + faces -- Ship external faces + draft -- Ship draft + trim -- Trim angle """ - # Hydrostatics computation - dispData = displacement(ship, draft, 0.0, trim, 0.0) + disp, B, cb = displacement(ship, draft=draft, trim=trim) if not faces: wet = 0.0 else: - wet = wettedArea(faces, draft, trim) - mom = moment(ship, draft, trim, dispData[0], dispData[1].x) - farea = FloatingArea(ship, draft, trim) - bm = BMT(ship, draft, trim) - cm = mainFrameCoeff(ship, draft) + wet = wettedArea(faces, draft=draft, trim=trim) + mom = moment(ship, draft=draft, trim=trim) + farea, cf = floatingArea(ship, draft=draft, trim=trim) + bm = BMT(ship, draft=draft, trim=trim) + cm = mainFrameCoeff(ship, draft=draft) # Store final data self.draft = draft self.trim = trim - self.disp = dispData[0] - self.xcb = dispData[1].x + self.disp = disp + self.xcb = Units.Quantity(B.x, Units.Length) self.wet = wet - self.farea = farea[0] + self.farea = farea self.mom = mom - self.KBt = dispData[1].z + self.KBt = Units.Quantity(B.z, Units.Length) self.BMt = bm - self.Cb = dispData[2] - self.Cf = farea[1] - self.Cm = cm + self.Cb = cb + self.Cf = cf + self.Cm = cm \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/__init__.py b/src/Mod/Ship/shipHydrostatics/__init__.py index 812df3a4a..c8f2b6eba 100644 --- a/src/Mod/Ship/shipHydrostatics/__init__.py +++ b/src/Mod/Ship/shipHydrostatics/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipLoadExample/TaskPanel.py b/src/Mod/Ship/shipLoadExample/TaskPanel.py index c2198351b..d9542c37a 100644 --- a/src/Mod/Ship/shipLoadExample/TaskPanel.py +++ b/src/Mod/Ship/shipLoadExample/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * @@ -44,7 +44,7 @@ class TaskPanel: App.open(path + "wigley.fcstd") elif(form.ship.currentIndex() == 2): # s60 (Katamaran) App.open(path + "s60_katamaran.fcstd") - elif(form.ship.currentIndex() == 2): # Wigley (Katamaran) + elif(form.ship.currentIndex() == 3): # Wigley (Katamaran) App.open(path + "wigley_katamaran.fcstd") return True diff --git a/src/Mod/Ship/shipLoadExample/__init__.py b/src/Mod/Ship/shipLoadExample/__init__.py index 4a5e9bc7b..3ae4067f5 100644 --- a/src/Mod/Ship/shipLoadExample/__init__.py +++ b/src/Mod/Ship/shipLoadExample/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipOutlineDraw/Preview.py b/src/Mod/Ship/shipOutlineDraw/Preview.py index cde6219a1..f8bdc815b 100644 --- a/src/Mod/Ship/shipOutlineDraw/Preview.py +++ b/src/Mod/Ship/shipOutlineDraw/Preview.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipOutlineDraw/TaskPanel.py b/src/Mod/Ship/shipOutlineDraw/TaskPanel.py index ce3f8bbf6..c900bfe74 100644 --- a/src/Mod/Ship/shipOutlineDraw/TaskPanel.py +++ b/src/Mod/Ship/shipOutlineDraw/TaskPanel.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipOutlineDraw/__init__.py b/src/Mod/Ship/shipOutlineDraw/__init__.py index 812df3a4a..c8f2b6eba 100644 --- a/src/Mod/Ship/shipOutlineDraw/__init__.py +++ b/src/Mod/Ship/shipOutlineDraw/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipUtils/Locale.py b/src/Mod/Ship/shipUtils/Locale.py index 3fb7fea7d..9cace4a76 100644 --- a/src/Mod/Ship/shipUtils/Locale.py +++ b/src/Mod/Ship/shipUtils/Locale.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipUtils/Math.py b/src/Mod/Ship/shipUtils/Math.py index 018467c58..fe4084f09 100644 --- a/src/Mod/Ship/shipUtils/Math.py +++ b/src/Mod/Ship/shipUtils/Math.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipUtils/Paths.py b/src/Mod/Ship/shipUtils/Paths.py index 6927be609..d1f8d0e9c 100644 --- a/src/Mod/Ship/shipUtils/Paths.py +++ b/src/Mod/Ship/shipUtils/Paths.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipUtils/Units.py b/src/Mod/Ship/shipUtils/Units.py index 0400685ff..de31462e9 100644 --- a/src/Mod/Ship/shipUtils/Units.py +++ b/src/Mod/Ship/shipUtils/Units.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/Mod/Ship/shipUtils/__init__.py b/src/Mod/Ship/shipUtils/__init__.py index 00b200f14..67a31832b 100644 --- a/src/Mod/Ship/shipUtils/__init__.py +++ b/src/Mod/Ship/shipUtils/__init__.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2011, 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * diff --git a/src/WindowsInstaller/ModShip.wxi b/src/WindowsInstaller/ModShip.wxi index 94e95bdb1..6c863a576 100644 --- a/src/WindowsInstaller/ModShip.wxi +++ b/src/WindowsInstaller/ModShip.wxi @@ -26,9 +26,11 @@ + + @@ -49,12 +51,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -85,6 +128,7 @@ +