From 925b861fc09010233a3ddd29f6c7c1e059f4b26a Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 13 Oct 2015 18:38:54 +0200 Subject: [PATCH 01/40] Generated the User Interface for the GZ tool --- src/Mod/Ship/shipGZ/TaskPanel.ui | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/Mod/Ship/shipGZ/TaskPanel.ui diff --git a/src/Mod/Ship/shipGZ/TaskPanel.ui b/src/Mod/Ship/shipGZ/TaskPanel.ui new file mode 100644 index 000000000..3db2d8d0b --- /dev/null +++ b/src/Mod/Ship/shipGZ/TaskPanel.ui @@ -0,0 +1,78 @@ + + + TaskPanel + + + + 0 + 0 + 260 + 256 + + + + GZ stability curve + + + + + + + + Number of points + + + + + + + + + + Variable Draft + + + true + + + + + + + + 0 + 0 + + + + Maximum angle + + + + + + + + + + Variable Trim angle + + + true + + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
From d63348acf9e6324aca1043af446edab866c1b443 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 13 Oct 2015 18:39:19 +0200 Subject: [PATCH 02/40] Started the GZ tool --- src/Mod/Ship/CMakeLists.txt | 16 +- src/Mod/Ship/InitGui.py | 7 +- src/Mod/Ship/ShipGui.py | 18 ++ src/Mod/Ship/shipGZ/PlotAux.py | 149 ++++++++++++ src/Mod/Ship/shipGZ/TaskPanel.py | 397 +++++++++++++++++++++++++++++++ src/Mod/Ship/shipGZ/__init__.py | 29 +++ 6 files changed, 610 insertions(+), 6 deletions(-) create mode 100644 src/Mod/Ship/shipGZ/PlotAux.py create mode 100644 src/Mod/Ship/shipGZ/TaskPanel.py create mode 100644 src/Mod/Ship/shipGZ/__init__.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index 4a3af6340..7ddd6fc4a 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -80,6 +80,14 @@ SET(ShipCapacityCurve_SRCS ) SOURCE_GROUP("shipcapacitycurve" FILES ${ShipCapacityCurve_SRCS}) +SET(ShipGZ_SRCS + shipGZ/__init__.py + shipGZ/PlotAux.py + shipGZ/TaskPanel.py + shipGZ/TaskPanel.ui +) +SOURCE_GROUP("shipgz" FILES ${ShipGZ_SRCS}) + SET(ShipUtils_SRCS shipUtils/__init__.py shipUtils/Locale.py @@ -89,7 +97,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} ${ShipGZ_SRCS} ${ShipUtils_SRCS}) ADD_CUSTOM_TARGET(Ship ALL SOURCES ${all_files} ${Ship_QRC_SRCS} @@ -156,6 +164,12 @@ INSTALL( DESTINATION Mod/Ship/shipCapacityCurve ) +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..832cb57d3 100644 --- a/src/Mod/Ship/InitGui.py +++ b/src/Mod/Ship/InitGui.py @@ -52,12 +52,9 @@ class ShipWorkbench(Workbench): "Ship_Hydrostatics"] weightslist = ["Ship_Weight", "Ship_Tank", - "Ship_Capacity"] - """ - weightslist = ["Ship_Weights", - "Ship_CreateTank", + "Ship_Capacity", "Ship_GZ"] - """ + self.appendToolbar( str(QtCore.QT_TRANSLATE_NOOP("Ship", "Ship design")), shiplist) diff --git a/src/Mod/Ship/ShipGui.py b/src/Mod/Ship/ShipGui.py index 515daa323..d179fdae9 100644 --- a/src/Mod/Ship/ShipGui.py +++ b/src/Mod/Ship/ShipGui.py @@ -170,6 +170,23 @@ class TankCapacity: '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 +195,4 @@ FreeCADGui.addCommand('Ship_Hydrostatics', Hydrostatics()) FreeCADGui.addCommand('Ship_Weight', CreateWeight()) FreeCADGui.addCommand('Ship_Tank', CreateTank()) FreeCADGui.addCommand('Ship_Capacity', TankCapacity()) +FreeCADGui.addCommand('Ship_GZ', GZ()) diff --git a/src/Mod/Ship/shipGZ/PlotAux.py b/src/Mod/Ship/shipGZ/PlotAux.py new file mode 100644 index 000000000..4d6427a71 --- /dev/null +++ b/src/Mod/Ship/shipGZ/PlotAux.py @@ -0,0 +1,149 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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 +from PySide import QtGui, QtCore +import FreeCAD +import FreeCADGui +from FreeCAD import Base +import Spreadsheet + +class Plot(object): + def __init__(self, x, y, disp, xcb, ship): + """ Constructor. performs the plot and shows it. + @param x X coordinates. + @param y Transversal computed areas. + @param disp Ship displacement. + @param xcb Bouyancy center length. + @param ship Active ship instance. + """ + self.plot(x, y, disp, xcb, ship) + self.spreadSheet(x, y, ship) + + def plot(self, x, y, disp, xcb, ship): + """ Perform the areas curve plot. + @param x X coordinates. + @param y Transversal areas. + @param disp Ship displacement. + @param xcb Bouyancy center length. + @param ship Active ship instance. + @return True if error happens. + """ + try: + import Plot + plt = Plot.figure('Areas curve') + 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 + # Plot areas curve + areas = Plot.plot(x, y, 'Transversal areas') + areas.line.set_linestyle('-') + areas.line.set_linewidth(2.0) + areas.line.set_color((0.0, 0.0, 0.0)) + # Get perpendiculars data + Lpp = ship.Length.getValueAs('m').Value + FPx = 0.5 * Lpp + APx = -0.5 * Lpp + maxArea = max(y) + # Plot perpendiculars + FP = Plot.plot([FPx, FPx], [0.0, maxArea]) + FP.line.set_linestyle('-') + FP.line.set_linewidth(1.0) + FP.line.set_color((0.0, 0.0, 0.0)) + AP = Plot.plot([APx, APx], [0.0, maxArea]) + AP.line.set_linestyle('-') + AP.line.set_linewidth(1.0) + AP.line.set_color((0.0, 0.0, 0.0)) + # Add annotations for prependiculars + ax = Plot.axes() + ax.annotate('AP', xy=(APx + 0.01 * Lpp, 0.01 * maxArea), size=15) + ax.annotate('AP', xy=(APx + 0.01 * Lpp, 0.95 * maxArea), size=15) + ax.annotate('FP', xy=(FPx + 0.01 * Lpp, 0.01 * maxArea), size=15) + ax.annotate('FP', xy=(FPx + 0.01 * Lpp, 0.95 * maxArea), size=15) + # Add some additional data + addInfo = ("$XCB = {0} \\; \\mathrm{{m}}$\n" + "$Area_{{max}} = {1} \\; \\mathrm{{m}}^2$\n" + "$\\bigtriangleup = {2} \\; \\mathrm{{tons}}$".format( + xcb, + maxArea, + disp)) + ax.text(0.0, + 0.01 * maxArea, + addInfo, + verticalalignment='bottom', + horizontalalignment='center', + fontsize=20) + # Write axes titles + Plot.xlabel(r'$x \; \mathrm{m}$') + Plot.ylabel(r'$Area \; \mathrm{m}^2$') + ax.xaxis.label.set_fontsize(20) + ax.yaxis.label.set_fontsize(20) + # Show grid + Plot.grid(True) + # End + plt.update() + return False + + def spreadSheet(self, x, y, ship): + """ Write the output data file. + @param x X coordinates. + @param y Transversal areas. + @param ship Active ship instance. + """ + s = FreeCAD.activeDocument().addObject('Spreadsheet::Sheet', + 'Areas curve') + + # Print the header + s.set("A1", "x [m]") + s.set("B1", "area [m^2]") + s.set("C1", "FP x") + s.set("D1", "FP y") + s.set("E1", "AP x") + s.set("F1", "AP y") + + # Print the perpendiculars data + Lpp = ship.Length.getValueAs('m').Value + FPx = 0.5 * Lpp + APx = -0.5 * Lpp + maxArea = max(y) + s.set("C2", str(FPx)) + s.set("D2", str(0.0)) + s.set("C3", str(FPx)) + s.set("D3", str(maxArea)) + s.set("E2", str(APx)) + s.set("F2", str(0.0)) + s.set("E3", str(APx)) + s.set("F3", str(maxArea)) + + # Print the data + for i in range(len(x)): + s.set("A{}".format(i + 2), str(x[i])) + s.set("B{}".format(i + 2), str(y[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..787bb2dc0 --- /dev/null +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -0,0 +1,397 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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 Preview +import PlotAux +import Instance +from shipUtils import Paths +import shipUtils.Units as USys +import shipUtils.Locale as Locale +from shipHydrostatics import Tools as Hydrostatics + + +class TaskPanel: + def __init__(self): + self.ui = Paths.modulePath() + "/shipAreasCurve/TaskPanel.ui" + self.preview = Preview.Preview() + self.ship = None + + def accept(self): + if not self.ship: + return False + self.save() + # Plot data + mw = self.getMainWindow() + 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 + data = Hydrostatics.areas(self.ship, + draft, + 0.0, + trim) + x = [] + y = [] + for i in range(0, len(data)): + x.append(data[i][0]) + y.append(data[i][1]) + PlotAux.Plot(x, y, disp, xcb, self.ship) + self.preview.clean() + return True + + def reject(self): + self.preview.clean() + 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.draft = self.widget(QtGui.QLineEdit, "Draft") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.output = self.widget(QtGui.QTextEdit, "OutputData") + form.doc = QtGui.QTextDocument(form.output) + self.form = form + if self.initValues(): + return True + self.retranslateUi() + QtCore.QObject.connect(form.draft, + QtCore.SIGNAL("valueChanged(double)"), + self.onData) + QtCore.QObject.connect(form.trim, + QtCore.SIGNAL("valueChanged(double)"), + self.onData) + + 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 + """ + 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 True + for i in range(0, len(selObjs)): + obj = selObjs[i] + props = obj.PropertiesList + try: + props.index("IsShip") + except ValueError: + continue + if obj.IsShip: + if self.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 + self.ship = obj + if not self.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 True + + length_format = USys.getLengthFormat() + angle_format = USys.getAngleFormat() + + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.draft = self.widget(QtGui.QLineEdit, "Draft") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + 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))) + # Try to use saved values + props = self.ship.PropertiesList + try: + props.index("AreaCurveDraft") + form.draft.setText(Locale.toString(length_format.format( + self.ship.AreaCurveDraft.getValueAs( + USys.getLengthUnits()).Value))) + except: + pass + try: + props.index("AreaCurveTrim") + form.trim.setText(Locale.toString(angle_format.format( + self.ship.AreaCurveTrim.getValueAs( + USys.getAngleUnits()).Value))) + except ValueError: + pass + # Update GUI + draft = Units.Quantity(form.draft.text()).getValueAs('m').Value + trim = Units.Quantity(form.trim.text()).getValueAs('deg').Value + self.preview.update(draft, trim, self.ship) + self.onUpdate() + 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_areas", + "Plot the transversal areas curve", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "DraftLabel").setText( + QtGui.QApplication.translate( + "ship_areas", + "Draft", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "TrimLabel").setText( + QtGui.QApplication.translate( + "ship_areas", + "Trim", + None, + QtGui.QApplication.UnicodeUTF8)) + + def clampLength(self, widget, val_min, val_max, val): + if val >= val_min and 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))) + return val + + def onData(self, value): + """ Method called when the tool input data is touched. + @param value Changed value. + """ + if not self.ship: + return + + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.draft = self.widget(QtGui.QLineEdit, "Draft") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + + # Get the values (or fix them in bad setting case) + try: + draft = Units.Quantity(Locale.fromString( + form.draft.text())).getValueAs('m').Value + 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))) + try: + trim = Units.Quantity(Locale.fromString( + form.trim.text())).getValueAs('deg').Value + 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))) + + 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) + + trim_min = -180.0 + trim_max = 180.0 + trim = self.clampAngle(form.trim, trim_min, trim_max, trim) + + self.onUpdate() + self.preview.update(draft, trim, self.ship) + + def onUpdate(self): + """ Method called when the data update is requested. """ + if not self.ship: + return + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.draft = self.widget(QtGui.QLineEdit, "Draft") + 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 + + # Calculate the drafts at each perpendicular + angle = math.radians(trim) + L = self.ship.Length.getValueAs('m').Value + B = self.ship.Breadth.getValueAs('m').Value + draftAP = draft + 0.5 * L * math.tan(angle) + if draftAP < 0.0: + draftAP = 0.0 + draftFP = draft - 0.5 * L * math.tan(angle) + if draftFP < 0.0: + draftFP = 0.0 + # Calculate the involved hydrostatics + data = Hydrostatics.displacement(self.ship, + draft, + 0.0, + trim) + # 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) + 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) + form.output.setHtml(string) + + def save(self): + """ Saves the data into ship instance. """ + mw = self.getMainWindow() + 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 + + props = self.ship.PropertiesList + try: + props.index("AreaCurveDraft") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_areas", + "Areas curve tool draft selected [m]", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "Areas curve tool draft selected [m]" + self.ship.addProperty("App::PropertyLength", + "AreaCurveDraft", + "Ship", + tooltip) + self.ship.AreaCurveDraft = '{} m'.format(draft) + try: + props.index("AreaCurveTrim") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_areas", + "Areas curve tool trim selected [deg]", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "Areas curve tool trim selected [deg]" + self.ship.addProperty("App::PropertyAngle", + "AreaCurveTrim", + "Ship", + tooltip) + self.ship.AreaCurveTrim = '{} deg'.format(trim) + + +def createTask(): + panel = TaskPanel() + Gui.Control.showDialog(panel) + if panel.setupUi(): + Gui.Control.closeDialog(panel) + return None + return panel diff --git a/src/Mod/Ship/shipGZ/__init__.py b/src/Mod/Ship/shipGZ/__init__.py new file mode 100644 index 000000000..812df3a4a --- /dev/null +++ b/src/Mod/Ship/shipGZ/__init__.py @@ -0,0 +1,29 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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() From b8ede9747a8f5feafacd38ca66ec4a8285b4fd8b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 11:13:09 +0200 Subject: [PATCH 03/40] Imported the loading conditions definition tool from sourceforge --- src/Mod/Ship/CMakeLists.txt | 13 +- src/Mod/Ship/InitGui.py | 1 + src/Mod/Ship/Instance.py | 9 + src/Mod/Ship/ShipGui.py | 18 + src/Mod/Ship/resources/Ship.qrc | 1 + .../resources/icons/Ship_LoadCondition.svg | 660 ++++++++++++++++++ .../Ship/shipCreateLoadCondition/__init__.py | 130 ++++ src/Mod/Ship/shipGZ/TaskPanel.py | 305 +------- 8 files changed, 857 insertions(+), 280 deletions(-) create mode 100644 src/Mod/Ship/resources/icons/Ship_LoadCondition.svg create mode 100644 src/Mod/Ship/shipCreateLoadCondition/__init__.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index 7ddd6fc4a..10f63449c 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -80,6 +80,11 @@ SET(ShipCapacityCurve_SRCS ) SOURCE_GROUP("shipcapacitycurve" FILES ${ShipCapacityCurve_SRCS}) +SET(ShipCreateLoadCondition_SRCS + shipCreateLoadCondition/__init__.py +) +SOURCE_GROUP("shipcreateloadcondition" FILES ${ShipCreateLoadCondition_SRCS}) + SET(ShipGZ_SRCS shipGZ/__init__.py shipGZ/PlotAux.py @@ -97,7 +102,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} ${ShipGZ_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} @@ -164,6 +169,12 @@ INSTALL( DESTINATION Mod/Ship/shipCapacityCurve ) +INSTALL( + FILES + ${ShipCreateLoadCondition_SRCS} + DESTINATION + Mod/Ship/shipCreateLoadCondition +) INSTALL( FILES ${ShipGZ_SRCS} diff --git a/src/Mod/Ship/InitGui.py b/src/Mod/Ship/InitGui.py index 832cb57d3..04ffd62fa 100644 --- a/src/Mod/Ship/InitGui.py +++ b/src/Mod/Ship/InitGui.py @@ -53,6 +53,7 @@ class ShipWorkbench(Workbench): weightslist = ["Ship_Weight", "Ship_Tank", "Ship_Capacity", + "Ship_LoadCondition", "Ship_GZ"] self.appendToolbar( diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index 0f4b0b81e..03ccf9503 100644 --- a/src/Mod/Ship/Instance.py +++ b/src/Mod/Ship/Instance.py @@ -109,6 +109,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 diff --git a/src/Mod/Ship/ShipGui.py b/src/Mod/Ship/ShipGui.py index d179fdae9..881157c2c 100644 --- a/src/Mod/Ship/ShipGui.py +++ b/src/Mod/Ship/ShipGui.py @@ -170,6 +170,23 @@ 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 @@ -195,4 +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/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/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py new file mode 100644 index 000000000..0f5bdc2fb --- /dev/null +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -0,0 +1,130 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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 +import Spreadsheet + + +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 + + # Create the spreadsheet + s = App.activeDocument().addObject('Spreadsheet::Sheet', + 'LoadCondition') + + # Add a reference to the owner ship + s.mergeCells('A1:D1') + s.setAlignment('A1:A1', 'center', 'keep') + s.setStyle('A1:A1', 'bold', 'add') + s.setStyle('A1:A1', 'underline', 'add') + s.set("A1", "SHIP") + s.set("A2", "ship") + s.set("A3", ship.Label) + s.set("B2", "internal ref") + s.set("B3", ship.Name) + s.setForeground('A1:B3', (0.5,0.5,0.5)) + + # Add the weights data + s.mergeCells('A4:D4') + s.setAlignment('A4:A4', 'center', 'keep') + s.setStyle('A4:A4', 'bold', 'add') + s.setStyle('A4:A4', 'underline', 'add') + s.set("A4", "WEIGHTS") + s.set("A5", "weight") + s.set("B5", "internal ref") + for i in range(len(ship.Weights)): + weight = App.activeDocument().getObject(ship.Weights[i]) + s.set("A{}".format(i + 6), weight.Label) + s.set("B{}".format(i + 6), weight.Name) + s.setForeground('A4:B{}'.format(5 + len(ship.Weights)), (0.5,0.5,0.5)) + + # Add the tanks data + s.mergeCells('A{0}:D{0}'.format(6 + len(ship.Weights))) + s.setAlignment('A{0}:A{0}'.format(6 + len(ship.Weights)), 'center', 'keep') + s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'bold', 'add') + s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'underline', 'add') + s.set("A{}".format(6 + len(ship.Weights)), "TANKS") + s.set("A{}".format(7 + len(ship.Weights)), "tank") + s.set("B{}".format(7 + len(ship.Weights)), "internal ref") + s.set("C{}".format(7 + len(ship.Weights)), "Fluid density [kg/m^3]") + s.set("D{}".format(7 + len(ship.Weights)), "Filling ratio") + for i in range(len(ship.Tanks)): + tank = App.activeDocument().getObject(ship.Tanks[i]) + s.set("A{}".format(i + 8 + len(ship.Weights)), tank.Label) + s.set("B{}".format(i + 8 + len(ship.Weights)), tank.Name) + s.set("C{}".format(i + 8 + len(ship.Weights)), "998.0") + s.set("D{}".format(i + 8 + len(ship.Weights)), "0.0") + s.setForeground('A{0}:A{0}'.format(6 + len(ship.Weights)), (0.5,0.5,0.5)) + s.setForeground('A{0}:D{0}'.format(7 + len(ship.Weights)), (0.5,0.5,0.5)) + s.setForeground('A{}:B{}'.format(8 + len(ship.Weights), + 8 + len(ship.Weights) + len(ship.Tanks)), + (0.5,0.5,0.5)) + + # Add the spreadsheet to the list of loading conditions of the ship + lcs = ship.LoadConditions[:] + lcs.append(s.Name) + ship.LoadConditions = lcs + + # Recompute to take the changes + App.activeDocument().recompute() \ No newline at end of file diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 787bb2dc0..d40b163de 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -26,51 +26,17 @@ import FreeCAD as App import FreeCADGui as Gui import Units from PySide import QtGui, QtCore -import Preview import PlotAux -import Instance from shipUtils import Paths import shipUtils.Units as USys import shipUtils.Locale as Locale -from shipHydrostatics import Tools as Hydrostatics class TaskPanel: def __init__(self): - self.ui = Paths.modulePath() + "/shipAreasCurve/TaskPanel.ui" - self.preview = Preview.Preview() - self.ship = None + self.ui = Paths.modulePath() + "/shipGZ/TaskPanel.ui" def accept(self): - if not self.ship: - return False - self.save() - # Plot data - mw = self.getMainWindow() - 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 - data = Hydrostatics.areas(self.ship, - draft, - 0.0, - trim) - x = [] - y = [] - for i in range(0, len(data)): - x.append(data[i][0]) - y.append(data[i][1]) - PlotAux.Plot(x, y, disp, xcb, self.ship) - self.preview.clean() return True def reject(self): @@ -102,20 +68,14 @@ class TaskPanel: mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.draft = self.widget(QtGui.QLineEdit, "Draft") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.output = self.widget(QtGui.QTextEdit, "OutputData") - form.doc = QtGui.QTextDocument(form.output) + form.angle = self.widget(QtGui.QLineEdit, "Angle") + form.n_points = self.widget(QtGui.QSpinBox, "NPoints") + form.var_draft = self.widget(QtGui.QCheckBox, "VariableDraft") + form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") self.form = form if self.initValues(): return True self.retranslateUi() - QtCore.QObject.connect(form.draft, - QtCore.SIGNAL("valueChanged(double)"), - self.onData) - QtCore.QObject.connect(form.trim, - QtCore.SIGNAL("valueChanged(double)"), - self.onData) def getMainWindow(self): toplevel = QtGui.qApp.topLevelWidgets() @@ -138,75 +98,6 @@ class TaskPanel: def initValues(self): """ Set initial values for fields """ - 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 True - for i in range(0, len(selObjs)): - obj = selObjs[i] - props = obj.PropertiesList - try: - props.index("IsShip") - except ValueError: - continue - if obj.IsShip: - if self.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 - self.ship = obj - if not self.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 True - - length_format = USys.getLengthFormat() - angle_format = USys.getAngleFormat() - - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.draft = self.widget(QtGui.QLineEdit, "Draft") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - 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))) - # Try to use saved values - props = self.ship.PropertiesList - try: - props.index("AreaCurveDraft") - form.draft.setText(Locale.toString(length_format.format( - self.ship.AreaCurveDraft.getValueAs( - USys.getLengthUnits()).Value))) - except: - pass - try: - props.index("AreaCurveTrim") - form.trim.setText(Locale.toString(angle_format.format( - self.ship.AreaCurveTrim.getValueAs( - USys.getAngleUnits()).Value))) - except ValueError: - pass - # Update GUI - draft = Units.Quantity(form.draft.text()).getValueAs('m').Value - trim = Units.Quantity(form.trim.text()).getValueAs('deg').Value - self.preview.update(draft, trim, self.ship) - self.onUpdate() return False def retranslateUi(self): @@ -214,178 +105,34 @@ class TaskPanel: mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.setWindowTitle(QtGui.QApplication.translate( - "ship_areas", - "Plot the transversal areas curve", + "ship_gz", + "Plot the GZ curve", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "DraftLabel").setText( + self.widget(QtGui.QLabel, "AngleLabel").setText( QtGui.QApplication.translate( - "ship_areas", - "Draft", + "ship_gz", + "Angle", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "TrimLabel").setText( + self.widget(QtGui.QLabel, "NPointsLabel").setText( QtGui.QApplication.translate( - "ship_areas", - "Trim", + "ship_gz", + "Number of points", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QCheckBox, "VariableDraft").setText( + QtGui.QApplication.translate( + "ship_gz", + "Variable draft", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QCheckBox, "VariableTrim").setText( + QtGui.QApplication.translate( + "ship_gz", + "Variable trim", None, QtGui.QApplication.UnicodeUTF8)) - - def clampLength(self, widget, val_min, val_max, val): - if val >= val_min and 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))) - return val - - def onData(self, value): - """ Method called when the tool input data is touched. - @param value Changed value. - """ - if not self.ship: - return - - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.draft = self.widget(QtGui.QLineEdit, "Draft") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - - # Get the values (or fix them in bad setting case) - try: - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - 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))) - try: - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value - 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))) - - 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) - - trim_min = -180.0 - trim_max = 180.0 - trim = self.clampAngle(form.trim, trim_min, trim_max, trim) - - self.onUpdate() - self.preview.update(draft, trim, self.ship) - - def onUpdate(self): - """ Method called when the data update is requested. """ - if not self.ship: - return - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.draft = self.widget(QtGui.QLineEdit, "Draft") - 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 - - # Calculate the drafts at each perpendicular - angle = math.radians(trim) - L = self.ship.Length.getValueAs('m').Value - B = self.ship.Breadth.getValueAs('m').Value - draftAP = draft + 0.5 * L * math.tan(angle) - if draftAP < 0.0: - draftAP = 0.0 - draftFP = draft - 0.5 * L * math.tan(angle) - if draftFP < 0.0: - draftFP = 0.0 - # Calculate the involved hydrostatics - data = Hydrostatics.displacement(self.ship, - draft, - 0.0, - trim) - # 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) - 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) - form.output.setHtml(string) - - def save(self): - """ Saves the data into ship instance. """ - mw = self.getMainWindow() - 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 - - props = self.ship.PropertiesList - try: - props.index("AreaCurveDraft") - except ValueError: - try: - tooltip = str(QtGui.QApplication.translate( - "ship_areas", - "Areas curve tool draft selected [m]", - None, - QtGui.QApplication.UnicodeUTF8)) - except: - tooltip = "Areas curve tool draft selected [m]" - self.ship.addProperty("App::PropertyLength", - "AreaCurveDraft", - "Ship", - tooltip) - self.ship.AreaCurveDraft = '{} m'.format(draft) - try: - props.index("AreaCurveTrim") - except ValueError: - try: - tooltip = str(QtGui.QApplication.translate( - "ship_areas", - "Areas curve tool trim selected [deg]", - None, - QtGui.QApplication.UnicodeUTF8)) - except: - tooltip = "Areas curve tool trim selected [deg]" - self.ship.addProperty("App::PropertyAngle", - "AreaCurveTrim", - "Ship", - tooltip) - self.ship.AreaCurveTrim = '{} deg'.format(trim) def createTask(): @@ -394,4 +141,4 @@ def createTask(): if panel.setupUi(): Gui.Control.closeDialog(panel) return None - return panel + return panel \ No newline at end of file From d28faee03a28fd4fa542161ec99233d1fc25d701 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 11:14:53 +0200 Subject: [PATCH 04/40] Imported the areas curve tool improvements from sourceforge --- src/Mod/Ship/shipAreasCurve/TaskPanel.py | 39 ++++++++++++++++++++++-- src/Mod/Ship/shipAreasCurve/TaskPanel.ui | 22 ++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.py b/src/Mod/Ship/shipAreasCurve/TaskPanel.py index 787bb2dc0..40719b935 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.py +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.py @@ -50,10 +50,12 @@ 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 + num = form.num.value() data = Hydrostatics.displacement(self.ship, draft, 0.0, @@ -63,7 +65,9 @@ class TaskPanel: data = Hydrostatics.areas(self.ship, draft, 0.0, - trim) + trim, + 0.0, + num) x = [] y = [] for i in range(0, len(data)): @@ -104,6 +108,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 +188,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 +208,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,7 +238,13 @@ 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)) @@ -347,11 +364,13 @@ 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 + num = form.num.value() props = self.ship.PropertiesList try: @@ -386,6 +405,22 @@ class TaskPanel: "Ship", tooltip) self.ship.AreaCurveTrim = '{} deg'.format(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(): 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 + + + From 02e908ae18201ec9f030d1740c74a303fefc5dc0 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 11:53:07 +0200 Subject: [PATCH 05/40] Added tooltips to the checkboxes --- src/Mod/Ship/shipGZ/TaskPanel.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index d40b163de..9f87d58d8 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -127,12 +127,29 @@ class TaskPanel: "Variable draft", None, QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QCheckBox, "VariableDraft").setTooltip( + QtGui.QApplication.translate( + "ship_gz", + "The ship will be moved to the equilibrium draft for each" + \ + " roll angle. It will significantly increase the required" + \ + " computing time", + 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 createTask(): From bd594136a0fbd1258d3e478ad2703c20978967ae Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 11:53:27 +0200 Subject: [PATCH 06/40] Added DATA identifier to the headers --- src/Mod/Ship/shipCreateLoadCondition/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index 0f5bdc2fb..3b79e6cd8 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -78,7 +78,7 @@ def load(): s.setAlignment('A1:A1', 'center', 'keep') s.setStyle('A1:A1', 'bold', 'add') s.setStyle('A1:A1', 'underline', 'add') - s.set("A1", "SHIP") + s.set("A1", "SHIP DATA") s.set("A2", "ship") s.set("A3", ship.Label) s.set("B2", "internal ref") @@ -90,7 +90,7 @@ def load(): s.setAlignment('A4:A4', 'center', 'keep') s.setStyle('A4:A4', 'bold', 'add') s.setStyle('A4:A4', 'underline', 'add') - s.set("A4", "WEIGHTS") + s.set("A4", "WEIGHTS DATA") s.set("A5", "weight") s.set("B5", "internal ref") for i in range(len(ship.Weights)): @@ -104,11 +104,11 @@ def load(): s.setAlignment('A{0}:A{0}'.format(6 + len(ship.Weights)), 'center', 'keep') s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'bold', 'add') s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'underline', 'add') - s.set("A{}".format(6 + len(ship.Weights)), "TANKS") + s.set("A{}".format(6 + len(ship.Weights)), "TANKS DATA") s.set("A{}".format(7 + len(ship.Weights)), "tank") s.set("B{}".format(7 + len(ship.Weights)), "internal ref") s.set("C{}".format(7 + len(ship.Weights)), "Fluid density [kg/m^3]") - s.set("D{}".format(7 + len(ship.Weights)), "Filling ratio") + s.set("D{}".format(7 + len(ship.Weights)), "Filling ratio (interval [0.0,1.0])") for i in range(len(ship.Tanks)): tank = App.activeDocument().getObject(ship.Tanks[i]) s.set("A{}".format(i + 8 + len(ship.Weights)), tank.Label) From 5467d0aec22396e3f65a2295b61bf530961b4b4b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 12:58:23 +0200 Subject: [PATCH 07/40] Created utilities to purge usless data inside the Ship instance --- src/Mod/Ship/Instance.py | 85 +++++++++++++++++++ .../Ship/shipCreateLoadCondition/__init__.py | 3 + src/Mod/Ship/shipCreateTank/TaskPanel.py | 3 + src/Mod/Ship/shipCreateWeight/TaskPanel.py | 3 + 4 files changed, 94 insertions(+) diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index 03ccf9503..d5e1f6f66 100644 --- a/src/Mod/Ship/Instance.py +++ b/src/Mod/Ship/Instance.py @@ -131,6 +131,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. diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index 3b79e6cd8..f115e7e95 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -125,6 +125,9 @@ def load(): lcs = ship.LoadConditions[:] lcs.append(s.Name) ship.LoadConditions = lcs + ship.Proxy.cleanWeights(ship) + ship.Proxy.cleanTanks(ship) + ship.Proxy.cleanLoadConditions(ship) # Recompute to take the changes App.activeDocument().recompute() \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateTank/TaskPanel.py b/src/Mod/Ship/shipCreateTank/TaskPanel.py index b97b95655..c060ba914 100644 --- a/src/Mod/Ship/shipCreateTank/TaskPanel.py +++ b/src/Mod/Ship/shipCreateTank/TaskPanel.py @@ -50,6 +50,9 @@ class TaskPanel: 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 True diff --git a/src/Mod/Ship/shipCreateWeight/TaskPanel.py b/src/Mod/Ship/shipCreateWeight/TaskPanel.py index 122deea52..6af196eb9 100644 --- a/src/Mod/Ship/shipCreateWeight/TaskPanel.py +++ b/src/Mod/Ship/shipCreateWeight/TaskPanel.py @@ -69,6 +69,9 @@ class TaskPanel: 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 True From 1d00b9f2505acceb3f6ccd829d739ed9b43ba24a Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 13:00:31 +0200 Subject: [PATCH 08/40] Fixed the unclaimed load conditions --- src/Mod/Ship/Instance.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index d5e1f6f66..08e434b7f 100644 --- a/src/Mod/Ship/Instance.py +++ b/src/Mod/Ship/Instance.py @@ -336,6 +336,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): From 1f74c1d413434eb6f266eb478445649ed64dcf4a Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 13:43:04 +0200 Subject: [PATCH 09/40] Filtered out the bad entities from the ship before the load condition generation --- src/Mod/Ship/shipCreateLoadCondition/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index f115e7e95..05276ebef 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -85,6 +85,10 @@ def load(): s.set("B3", ship.Name) s.setForeground('A1:B3', (0.5,0.5,0.5)) + # Clean the Ship instance before generating the load condition + ship.Proxy.cleanWeights(ship) + ship.Proxy.cleanTanks(ship) + # Add the weights data s.mergeCells('A4:D4') s.setAlignment('A4:A4', 'center', 'keep') @@ -125,8 +129,6 @@ def load(): lcs = ship.LoadConditions[:] lcs.append(s.Name) ship.LoadConditions = lcs - ship.Proxy.cleanWeights(ship) - ship.Proxy.cleanTanks(ship) ship.Proxy.cleanLoadConditions(ship) # Recompute to take the changes From ee070344eed08456c2ca89892e14f657e17b9e2e Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 16 Oct 2015 13:50:00 +0200 Subject: [PATCH 10/40] Slightly improved the load condition data sheet --- src/Mod/Ship/shipCreateLoadCondition/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index 05276ebef..f059e8ffa 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -75,9 +75,9 @@ def load(): # Add a reference to the owner ship s.mergeCells('A1:D1') - s.setAlignment('A1:A1', 'center', 'keep') - s.setStyle('A1:A1', 'bold', 'add') - s.setStyle('A1:A1', 'underline', 'add') + s.setAlignment('A1:B2', 'center', 'keep') + s.setStyle('A1:B2', 'bold', 'add') + s.setStyle('A1:B2', 'underline', 'add') s.set("A1", "SHIP DATA") s.set("A2", "ship") s.set("A3", ship.Label) @@ -91,9 +91,9 @@ def load(): # Add the weights data s.mergeCells('A4:D4') - s.setAlignment('A4:A4', 'center', 'keep') - s.setStyle('A4:A4', 'bold', 'add') - s.setStyle('A4:A4', 'underline', 'add') + s.setAlignment('A4:B5', 'center', 'keep') + s.setStyle('A4:B5', 'bold', 'add') + s.setStyle('A4:B5', 'underline', 'add') s.set("A4", "WEIGHTS DATA") s.set("A5", "weight") s.set("B5", "internal ref") @@ -106,8 +106,11 @@ def load(): # Add the tanks data s.mergeCells('A{0}:D{0}'.format(6 + len(ship.Weights))) s.setAlignment('A{0}:A{0}'.format(6 + len(ship.Weights)), 'center', 'keep') + s.setAlignment('A{0}:D{0}'.format(7 + len(ship.Weights)), 'center', 'keep') s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'bold', 'add') + s.setStyle('A{0}:D{0}'.format(7 + len(ship.Weights)), 'bold', 'add') s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'underline', 'add') + s.setStyle('A{0}:D{0}'.format(7 + len(ship.Weights)), 'underline', 'add') s.set("A{}".format(6 + len(ship.Weights)), "TANKS DATA") s.set("A{}".format(7 + len(ship.Weights)), "tank") s.set("B{}".format(7 + len(ship.Weights)), "internal ref") From 9b6b171b045c9913fdc91ca20dd98e3301c0d8bb Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 20 Oct 2015 13:05:27 +0200 Subject: [PATCH 11/40] Using the smart expression in the spreadsheet to manage the entities labels --- .../Ship/shipCreateLoadCondition/__init__.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/Mod/Ship/shipCreateLoadCondition/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index f059e8ffa..4a06fba41 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -26,6 +26,10 @@ import FreeCADGui as Gui import Spreadsheet +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 @@ -73,6 +77,55 @@ def load(): s = App.activeDocument().addObject('Spreadsheet::Sheet', 'LoadCondition') + # Add a description + s.setForeground('A1:B2', READ_ONLY_FOREGROUND) + s.setBackground('A1:B2', READ_ONLY_BACKGROUND) + s.setAlignment('B1:B2', 'center', 'keep') + s.setStyle('B1:B2', 'italic', 'add') + s.set("A1", "Ship:") + s.set("A2", "Load condition:") + s.set("B1", "=" + ship.Name + ".Label") + s.set("B2", "=Label") + + # Add the weights data + s.setAlignment('A4:A5', 'center', 'keep') + s.setStyle('A4:A5', 'bold', 'add') + s.setStyle('A4:A5', 'underline', 'add') + s.set("A4", "WEIGHTS DATA") + s.set("A5", "name") + for i in range(len(ship.Weights)): + weight = App.activeDocument().getObject(ship.Weights[i]) + s.set("A{}".format(i + 6), "=" + weight.Name + ".Label") + s.setForeground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_FOREGROUND) + s.setBackground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_BACKGROUND) + + # Add the tanks data + s.mergeCells('C4:E4') + s.setForeground('C4:E5', READ_ONLY_FOREGROUND) + s.setBackground('C4:E5', READ_ONLY_BACKGROUND) + s.setAlignment('C4:E5', 'center', 'keep') + s.setStyle('C4:E5', 'bold', 'add') + s.setStyle('C4:E5', 'underline', 'add') + s.set("C4", "TANKS DATA") + s.set("C5", "name") + s.set("D5", "Fluid density [kg/m^3]") + s.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]) + s.set("C{}".format(i + 6), "=" + tank.Name + ".Label") + s.set("D{}".format(i + 6), "998.0") + s.set("E{}".format(i + 6), "0.0") + s.setForeground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_FOREGROUND) + s.setBackground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_BACKGROUND) + + s.setColumnWidth('A', 128) + s.setColumnWidth('B', 128) + s.setColumnWidth('C', 128) + s.setColumnWidth('D', 150) + s.setColumnWidth('E', 200) + + """ # Add a reference to the owner ship s.mergeCells('A1:D1') s.setAlignment('A1:B2', 'center', 'keep') @@ -127,6 +180,7 @@ def load(): s.setForeground('A{}:B{}'.format(8 + len(ship.Weights), 8 + len(ship.Weights) + len(ship.Tanks)), (0.5,0.5,0.5)) + """ # Add the spreadsheet to the list of loading conditions of the ship lcs = ship.LoadConditions[:] From 74ed253f51032bf7f1940f915860006611b47eb7 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 20 Oct 2015 13:09:40 +0200 Subject: [PATCH 12/40] Improved the objects representation --- src/Mod/Ship/TankInstance.py | 2 +- src/Mod/Ship/WeightInstance.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index 65e7ea182..27b3c9250 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -184,7 +184,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..cac299967 100644 --- a/src/Mod/Ship/WeightInstance.py +++ b/src/Mod/Ship/WeightInstance.py @@ -312,7 +312,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 From 3ea9f6245d1e9614725555f944e76a65a49308c2 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 13:01:23 +0100 Subject: [PATCH 13/40] Removed the uneeded pivy imports --- src/Mod/Ship/Instance.py | 2 -- src/Mod/Ship/TankInstance.py | 2 -- src/Mod/Ship/WeightInstance.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index 08e434b7f..f22eda010 100644 --- a/src/Mod/Ship/Instance.py +++ b/src/Mod/Ship/Instance.py @@ -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 diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index 27b3c9250..e50831e38 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -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 diff --git a/src/Mod/Ship/WeightInstance.py b/src/Mod/Ship/WeightInstance.py index cac299967..746e5f136 100644 --- a/src/Mod/Ship/WeightInstance.py +++ b/src/Mod/Ship/WeightInstance.py @@ -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 From ce52c4b0243b6423d4167db1a1fa96a882f13584 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 13:10:53 +0100 Subject: [PATCH 14/40] Set a different color to the z-vol curve (in order to make possible3 to differentiate that from the level-vol curve) --- src/Mod/Ship/shipCapacityCurve/PlotAux.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/shipCapacityCurve/PlotAux.py b/src/Mod/Ship/shipCapacityCurve/PlotAux.py index 2b58dcab1..81e0595b8 100644 --- a/src/Mod/Ship/shipCapacityCurve/PlotAux.py +++ b/src/Mod/Ship/shipCapacityCurve/PlotAux.py @@ -80,7 +80,7 @@ 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') @@ -89,11 +89,13 @@ class Plot(object): vols = Plot.plot(z, v, 'Capacity') 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 From cf76a9299687025647bc3c078d73e47f620e952d Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 13:15:05 +0100 Subject: [PATCH 15/40] Added console messages to know the computation progress --- src/Mod/Ship/shipCapacityCurve/TaskPanel.py | 470 ++++++++++++++++++-- 1 file changed, 429 insertions(+), 41 deletions(-) diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index 18a8c5ec4..5f210b3a8 100644 --- a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py +++ b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py @@ -21,31 +21,108 @@ #* * #*************************************************************************** + import math import FreeCAD as App import FreeCADGui as Gui +from FreeCAD import Base, Vector +import Part import Units from PySide import QtGui, QtCore import PlotAux -import TankInstance as Instance +import Instance from shipUtils import Paths import shipUtils.Units as USys +import shipUtils.Locale as Locale +import Tools class TaskPanel: def __init__(self): - self.ui = Paths.modulePath() + "/shipCapacityCurve/TaskPanel.ui" - self.tank = None + self.ui = Paths.modulePath() + "/shipHydrostatics/TaskPanel.ui" + self.ship = None + self.running = False def accept(self): - if self.tank is None: + if not self.ship: return False - # Plot data - l, z, v = self.compute() - PlotAux.Plot(l, z, v, self.tank) + if self.running: + return + self.save() + + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") + 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 + n_draft = form.nDraft.value() + + draft = min_draft + drafts = [draft] + dDraft = (max_draft - min_draft) / (n_draft - 1) + for i in range(1, n_draft): + draft = draft + dDraft + drafts.append(draft) + + # Compute data + # Get external faces + self.loop = QtCore.QEventLoop() + self.timer = QtCore.QTimer() + self.timer.setSingleShot(True) + QtCore.QObject.connect(self.timer, + QtCore.SIGNAL("timeout()"), + self.loop, + QtCore.SLOT("quit()")) + self.running = True + faces = self.externalFaces(self.ship.Shape) + if not self.running: + return False + if len(faces) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Failure detecting external faces from the ship object", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + return False + faces = Part.makeShell(faces) + # Get the hydrostatics + msg = QtGui.QApplication.translate( + "ship_console", + "Computing hydrostatics", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintMessage(msg + '...\n') + points = [] + for i in range(len(drafts)): + App.Console.PrintMessage("\t{} / {}\n".format(i + 1, len(drafts))) + draft = drafts[i] + point = Tools.Point(self.ship, + faces, + draft, + trim) + points.append(point) + self.timer.start(0.0) + self.loop.exec_() + if(not self.running): + break + PlotAux.Plot(self.ship, trim, points) return True def reject(self): + if not self.ship: + return False + if self.running: + self.running = False + return return True def clicked(self, index): @@ -72,18 +149,32 @@ class TaskPanel: def setupUi(self): mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.points = self.widget(QtGui.QSpinBox, "Points") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") + form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") + form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") self.form = form + # Initial values if self.initValues(): return True self.retranslateUi() + # Connect Signals and Slots + QtCore.QObject.connect(form.trim, + QtCore.SIGNAL("valueChanged(double)"), + self.onData) + QtCore.QObject.connect(form.minDraft, + QtCore.SIGNAL("valueChanged(double)"), + self.onData) + QtCore.QObject.connect(form.maxDraft, + QtCore.SIGNAL("valueChanged(double)"), + self.onData) def getMainWindow(self): toplevel = QtGui.qApp.topLevelWidgets() for i in toplevel: if i.metaObject().className() == "Gui::MainWindow": return i - raise Exception("No main window found") + raise RuntimeError("No main window found") def widget(self, class_id, name): """Return the selected widget. @@ -99,82 +190,379 @@ class TaskPanel: def initValues(self): """ Set initial values for fields """ + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") + form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") + form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") + selObjs = Gui.Selection.getSelection() if not selObjs: msg = QtGui.QApplication.translate( "ship_console", - "A tank instance must be selected before using this tool (no" + "A ship 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(0, len(selObjs)): + for i in range(len(selObjs)): obj = selObjs[i] props = obj.PropertiesList try: - props.index("IsTank") + props.index("IsShip") except ValueError: continue - if obj.IsTank: - if self.tank: + if obj.IsShip: + if self.ship: msg = QtGui.QApplication.translate( "ship_console", - "More than one tank have been selected (the extra" - " tanks will be ignored)", + "More than one ship have been selected (the extra" + " ships will be ignored)", None, QtGui.QApplication.UnicodeUTF8) App.Console.PrintWarning(msg + '\n') break - self.tank = obj - if not self.tank: + self.ship = obj + + if not self.ship: msg = QtGui.QApplication.translate( "ship_console", - "A tank instance must be selected before using this tool (no" - " valid tank found at the selected objects)", + "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 True + + props = self.ship.PropertiesList + + length_format = USys.getLengthFormat() + angle_format = USys.getAngleFormat() + + try: + props.index("HydrostaticsTrim") + form.trim.setText(Locale.toString(angle_format.format( + self.ship.HydrostaticsTrim.getValueAs( + USys.getLengthUnits()).Value))) + except ValueError: + form.trim.setText(Locale.toString(angle_format.format(0.0))) + + try: + props.index("HydrostaticsMinDraft") + form.minDraft.setText(Locale.toString(length_format.format( + self.ship.HydrostaticsMinDraft.getValueAs( + USys.getLengthUnits()).Value))) + except ValueError: + form.minDraft.setText(Locale.toString(length_format.format( + 0.9 * self.ship.Draft.getValueAs(USys.getLengthUnits()).Value))) + try: + props.index("HydrostaticsMaxDraft") + form.maxDraft.setText(Locale.toString(length_format.format( + self.ship.HydrostaticsMaxDraft.getValueAs( + USys.getLengthUnits()).Value))) + except ValueError: + form.maxDraft.setText(Locale.toString(length_format.format( + 1.1 * self.ship.Draft.getValueAs(USys.getLengthUnits()).Value))) + + try: + props.index("HydrostaticsNDraft") + form.nDraft.setValue(self.ship.HydrostaticsNDraft) + except ValueError: + pass + return False def retranslateUi(self): - """ Set user interface locale strings. """ + """ Set user interface locale strings. + """ mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.setWindowTitle(QtGui.QApplication.translate( - "ship_capacity", - "Plot the tank capacity curve", + "ship_hydrostatic", + "Plot hydrostatics", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "PointsLabel").setText( + self.widget(QtGui.QLabel, "TrimLabel").setText( QtGui.QApplication.translate( - "ship_capacity", + "ship_hydrostatic", + "Trim", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "MinDraftLabel").setText( + QtGui.QApplication.translate( + "ship_hydrostatic", + "Minimum draft", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "MaxDraftLabel").setText( + QtGui.QApplication.translate( + "ship_hydrostatic", + "Maximum draft", + None, + QtGui.QApplication.UnicodeUTF8)) + self.widget(QtGui.QLabel, "NDraftLabel").setText( + QtGui.QApplication.translate( + "ship_hydrostatic", "Number of points", None, QtGui.QApplication.UnicodeUTF8)) - def compute(self): + def clampLength(self, widget, val_min, val_max, val): + if val >= val_min and 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))) + return val + + def onData(self, value): + """ Method called when input data is changed. + @param value Changed value. + """ + if not self.ship: + return mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.points = self.widget(QtGui.QSpinBox, "Points") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") + form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") - bbox = self.tank.Shape.BoundBox - dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length) + # Get the values (or fix them in bad setting case) + try: + trim = Units.Quantity(Locale.fromString( + form.trim.text())).getValueAs('deg').Value + except: + trim = 0.0 + input_format = USys.getAngleFormat() + qty = Units.Quantity('{} deg'.format(trim)) + form.trim.setText(Locale.toString(input_format.format( + qty.getValueAs(USys.getLengthUnits()).Value))) + try: + min_draft = Units.Quantity(Locale.fromString( + form.minDraft.text())).getValueAs('m').Value + except: + min_draft = 0.9 * self.ship.Draft.getValueAs('m').Value + input_format = USys.getLengthFormat() + qty = Units.Quantity('{} m'.format(min_draft)) + form.minDraft.setText(Locale.toString(input_format.format( + qty.getValueAs(USys.getLengthUnits()).Value))) + try: + max_draft = Units.Quantity(Locale.fromString( + form.minDraft.text())).getValueAs('m').Value + except: + max_draft = 0.9 * self.ship.Draft.getValueAs('m').Value + input_format = USys.getLengthFormat() + qty = Units.Quantity('{} m'.format(max_draft)) + form.maxDraft.setText(Locale.toString(input_format.format( + qty.getValueAs(USys.getLengthUnits()).Value))) - n = form.points.value() - dlevel = 100.0 / (n - 1) - l = [0.0] - v = [0.0] - z = [0.0] + # Clamp the values to the bounds + bbox = self.ship.Shape.BoundBox + draft_min = bbox.ZMin / Units.Metre.Value + draft_max = bbox.ZMax / Units.Metre.Value + min_draft = self.clampLength(form.minDraft, + draft_min, + draft_max, + min_draft) + max_draft = self.clampLength(form.maxDraft, + draft_min, + draft_max, + max_draft) + trim_min = -180.0 + trim_max = 180.0 + trim = self.clampAngle(form.trim, trim_min, trim_max, trim) - 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) + # Clamp draft values to assert that the minimum value is lower than + # the maximum one + min_draft = self.clampLength(form.minDraft, + draft_min, + max_draft, + min_draft) + max_draft = self.clampLength(form.maxDraft, + min_draft, + draft_max, + max_draft) + + + def save(self): + """ Saves data into ship instance. + """ + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskPanel") + form.trim = self.widget(QtGui.QLineEdit, "Trim") + form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") + 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 + n_draft = form.nDraft.value() + + props = self.ship.PropertiesList + try: + props.index("HydrostaticsTrim") + except ValueError: + tooltip = str(QtGui.QApplication.translate( + "ship_hydrostatic", + "Hydrostatics tool trim selected", + None, + QtGui.QApplication.UnicodeUTF8)) + self.ship.addProperty("App::PropertyAngle", + "HydrostaticsTrim", + "Ship", + tooltip) + self.ship.HydrostaticsTrim = '{} deg'.format(trim) + + try: + props.index("HydrostaticsMinDraft") + except ValueError: + tooltip = str(QtGui.QApplication.translate( + "ship_hydrostatic", + "Hydrostatics tool minimum draft selected [m]", + None, + QtGui.QApplication.UnicodeUTF8)) + self.ship.addProperty("App::PropertyLength", + "HydrostaticsMinDraft", + "Ship", + tooltip) + self.ship.HydrostaticsMinDraft = '{} m'.format(min_draft) + + try: + props.index("HydrostaticsMaxDraft") + except ValueError: + tooltip = str(QtGui.QApplication.translate( + "ship_hydrostatic", + "Hydrostatics tool maximum draft selected [m]", + None, + QtGui.QApplication.UnicodeUTF8)) + self.ship.addProperty("App::PropertyLength", + "HydrostaticsMaxDraft", + "Ship", + tooltip) + self.ship.HydrostaticsMaxDraft = '{} m'.format(max_draft) + + try: + props.index("HydrostaticsNDraft") + except ValueError: + tooltip = str(QtGui.QApplication.translate( + "ship_hydrostatic", + "Hydrostatics tool number of points selected", + None, + QtGui.QApplication.UnicodeUTF8)) + self.ship.addProperty("App::PropertyInteger", + "HydrostaticsNDraft", + "Ship", + tooltip) + self.ship.HydrostaticsNDraft = form.nDraft.value() + + def lineFaceSection(self, line, surface): + """ Returns the point of section of a line with a face + @param line Line object, that can be a curve. + @param surface Surface object (must be a Part::Shape) + @return Section points array, [] if line don't cut surface + """ + result = [] + vertexes = line.Vertexes + nVertex = len(vertexes) + + section = line.cut(surface) + + points = section.Vertexes + return points + + def externalFaces(self, shape): + """ Returns detected external faces. + @param shape Shape where external faces wanted. + @return List of external faces detected. + """ + result = [] + faces = shape.Faces + bbox = shape.BoundBox + L = bbox.XMax - bbox.XMin + B = bbox.YMax - bbox.YMin + T = bbox.ZMax - bbox.ZMin + dist = math.sqrt(L*L + B*B + T*T) + msg = QtGui.QApplication.translate( + "ship_console", + "Computing external faces", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintMessage(msg + '...\n') + # Valid/unvalid faces detection loop + for i in range(len(faces)): + App.Console.PrintMessage("\t{} / {}\n".format(i + 1, len(faces))) + f = faces[i] + # Create a line normal to surface at middle point + u = 0.0 + v = 0.0 + try: + surf = f.Surface + u = 0.5*(surf.getUKnots()[0]+surf.getUKnots()[-1]) + v = 0.5*(surf.getVKnots()[0]+surf.getVKnots()[-1]) + except: + cog = f.CenterOfMass + [u, v] = f.Surface.parameter(cog) + p0 = f.valueAt(u, v) + try: + n = f.normalAt(u, v).normalize() + except: + continue + p1 = p0 + n.multiply(1.5 * dist) + line = Part.makeLine(p0, p1) + # Look for faces in front of this + nPoints = 0 + for j in range(len(faces)): + f2 = faces[j] + section = self.lineFaceSection(line, f2) + if len(section) <= 2: + continue + # Add points discarding start and end + nPoints = nPoints + len(section) - 2 + # In order to avoid special directions we can modify line + # normal a little bit. + angle = 5 + line.rotate(p0, Vector(1, 0, 0), angle) + line.rotate(p0, Vector(0, 1, 0), angle) + line.rotate(p0, Vector(0, 0, 1), angle) + nPoints2 = 0 + for j in range(len(faces)): + if i == j: + continue + f2 = faces[j] + section = self.lineFaceSection(line, f2) + if len(section) <= 2: + continue + # Add points discarding start and end + nPoints2 = nPoints + len(section) - 2 + # If the number of intersection points is pair, is a + # external face. So if we found an odd points intersection, + # face must be discarded. + if (nPoints % 2) or (nPoints2 % 2): + continue + result.append(f) + self.timer.start(0.0) + self.loop.exec_() + if(not self.running): + break + return result def createTask(): From c99f4921cacecfc4e540d54cbcff0cdc4b1f97fa Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 14:41:50 +0100 Subject: [PATCH 16/40] Implemented GZ tool variables saving and loading --- src/Mod/Ship/shipGZ/TaskPanel.py | 195 ++++++++++++++++++++++++++++++- src/Mod/Ship/shipGZ/TaskPanel.ui | 9 +- 2 files changed, 197 insertions(+), 7 deletions(-) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 9f87d58d8..3737a5250 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -37,10 +37,12 @@ class TaskPanel: self.ui = Paths.modulePath() + "/shipGZ/TaskPanel.ui" def accept(self): + if self.lc is None: + return False + self.save() return True def reject(self): - self.preview.clean() return True def clicked(self, index): @@ -69,7 +71,7 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.angle = self.widget(QtGui.QLineEdit, "Angle") - form.n_points = self.widget(QtGui.QSpinBox, "NPoints") + form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") form.var_draft = self.widget(QtGui.QCheckBox, "VariableDraft") form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") self.form = form @@ -98,6 +100,108 @@ class TaskPanel: 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')) + ship = None + for s in ships: + if s is None or not s.PropertiesList.index("IsShip"): + continue + ship = s + break + if ship is None: + 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_draft = self.widget(QtGui.QCheckBox, "VariableDraft") + 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("GZVariableDraft") + if self.ship.GZVariableDraft: + form.var_draft.setCheckState(QtCore.Qt.Checked) + else: + form.var_draft.setCheckState(QtCore.Qt.Unchecked) + 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): @@ -112,10 +216,10 @@ class TaskPanel: self.widget(QtGui.QLabel, "AngleLabel").setText( QtGui.QApplication.translate( "ship_gz", - "Angle", + "Maximum angle", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "NPointsLabel").setText( + self.widget(QtGui.QLabel, "NumPointsLabel").setText( QtGui.QApplication.translate( "ship_gz", "Number of points", @@ -127,7 +231,7 @@ class TaskPanel: "Variable draft", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QCheckBox, "VariableDraft").setTooltip( + self.widget(QtGui.QCheckBox, "VariableDraft").setToolTip( QtGui.QApplication.translate( "ship_gz", "The ship will be moved to the equilibrium draft for each" + \ @@ -141,7 +245,7 @@ class TaskPanel: "Variable trim", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QCheckBox, "VariableTrim").setTooltip( + self.widget(QtGui.QCheckBox, "VariableTrim").setToolTip( QtGui.QApplication.translate( "ship_gz", "The ship will be rotated to the equilibrium trim angle for" + \ @@ -150,7 +254,86 @@ class TaskPanel: 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_draft = self.widget(QtGui.QCheckBox, "VariableDraft") + 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_draft = form.var_draft.isChecked() + 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_areas", + "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("GZVariableDraft") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_areas", + "GZ curve tool variable draft selection", + None, + QtGui.QApplication.UnicodeUTF8)) + except: + tooltip = "GZ curve tool variable draft selection" + self.ship.addProperty("App::PropertyBool", + "GZVariableDraft", + "Ship", + tooltip) + self.ship.GZVariableDraft = var_draft + try: + props.index("GZVariableTrim") + except ValueError: + try: + tooltip = str(QtGui.QApplication.translate( + "ship_areas", + "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() diff --git a/src/Mod/Ship/shipGZ/TaskPanel.ui b/src/Mod/Ship/shipGZ/TaskPanel.ui index 3db2d8d0b..36fa605a3 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.ui +++ b/src/Mod/Ship/shipGZ/TaskPanel.ui @@ -24,7 +24,14 @@ - + + + 2 + + + 11 + + From 7a53c17fdefb64080b44af9d3ff748b1be039fa8 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 15:38:44 +0100 Subject: [PATCH 17/40] Added a weights and tanks collector --- src/Mod/Ship/shipGZ/TaskPanel.py | 125 +++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 3737a5250..f54d7d428 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -127,16 +127,125 @@ class TaskPanel: if obj not in doc.getObjectsByLabel(obj.get('B2')): continue ships = doc.getObjectsByLabel(obj.get('B1')) - ship = None - for s in ships: - if s is None or not s.PropertiesList.index("IsShip"): - continue - ship = s - break - if ship is None: + 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 + # Extract the weights and the tanks + weights = [] + index = 6 + while True: + try: + ws = doc.getObjectsByLabel(obj.get('A{}'.format(index))) + except ValueError: + break + index += 1 + if len(ws) != 1: + if len(ws) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Weight label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + obj.get('A{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Weight label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + obj.get('A{}'.format(index - 1)))) + continue + w = ws[0] + try: + if w is None or not w.PropertiesList.index("IsWeight"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Weight! (the object labeled as" + "'{}' is not a weight)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + obj.get('A{}'.format(index - 1)))) + continue + except ValueError: + continue + weights.append(w) + tanks = [] + index = 6 + while True: + try: + ts = doc.getObjectsByLabel(obj.get('C{}'.format(index))) + dens = float(obj.get('D{}'.format(index))) + level = float(obj.get('E{}'.format(index))) + except ValueError: + break + index += 1 + if len(ts) != 1: + if len(ts) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Tank label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + obj.get('C{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Tank label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ts), + obj.get('C{}'.format(index - 1)))) + continue + t = ts[0] + try: + if t is None or not t.PropertiesList.index("IsTank"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Tank! (the object labeled as" + "'{}' is not a tank)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + obj.get('C{}'.format(index - 1)))) + continue + except ValueError: + continue + tanks.append((t, dens, level)) # Let's see if several loading conditions have been selected (and # prompt a warning) if self.lc: @@ -150,6 +259,8 @@ class TaskPanel: break self.lc = obj self.ship = ship + self.weights = weights + self.tanks = tanks if not self.lc: msg = QtGui.QApplication.translate( "ship_console", From a0a2a1012ed96b0640067f2ad0dcdabb9b3837bc Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 16:11:02 +0100 Subject: [PATCH 18/40] Fixed typo in the capacity curve tool --- src/Mod/Ship/shipCapacityCurve/TaskPanel.py | 467 ++------------------ 1 file changed, 43 insertions(+), 424 deletions(-) diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index 5f210b3a8..c4c61ab90 100644 --- a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py +++ b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py @@ -21,108 +21,31 @@ #* * #*************************************************************************** - import math import FreeCAD as App import FreeCADGui as Gui -from FreeCAD import Base, Vector -import Part import Units from PySide import QtGui, QtCore import PlotAux -import Instance +import TankInstance as Instance from shipUtils import Paths import shipUtils.Units as USys -import shipUtils.Locale as Locale -import Tools class TaskPanel: def __init__(self): - self.ui = Paths.modulePath() + "/shipHydrostatics/TaskPanel.ui" - self.ship = None - self.running = False + self.ui = Paths.modulePath() + "/shipCapacityCurve/TaskPanel.ui" + self.tank = None def accept(self): - if not self.ship: + if self.tank is None: return False - if self.running: - return - self.save() - - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") - 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 - n_draft = form.nDraft.value() - - draft = min_draft - drafts = [draft] - dDraft = (max_draft - min_draft) / (n_draft - 1) - for i in range(1, n_draft): - draft = draft + dDraft - drafts.append(draft) - - # Compute data - # Get external faces - self.loop = QtCore.QEventLoop() - self.timer = QtCore.QTimer() - self.timer.setSingleShot(True) - QtCore.QObject.connect(self.timer, - QtCore.SIGNAL("timeout()"), - self.loop, - QtCore.SLOT("quit()")) - self.running = True - faces = self.externalFaces(self.ship.Shape) - if not self.running: - return False - if len(faces) == 0: - msg = QtGui.QApplication.translate( - "ship_console", - "Failure detecting external faces from the ship object", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n') - return False - faces = Part.makeShell(faces) - # Get the hydrostatics - msg = QtGui.QApplication.translate( - "ship_console", - "Computing hydrostatics", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintMessage(msg + '...\n') - points = [] - for i in range(len(drafts)): - App.Console.PrintMessage("\t{} / {}\n".format(i + 1, len(drafts))) - draft = drafts[i] - point = Tools.Point(self.ship, - faces, - draft, - trim) - points.append(point) - self.timer.start(0.0) - self.loop.exec_() - if(not self.running): - break - PlotAux.Plot(self.ship, trim, points) + # Plot data + l, z, v = self.compute() + PlotAux.Plot(l, z, v, self.tank) return True def reject(self): - if not self.ship: - return False - if self.running: - self.running = False - return return True def clicked(self, index): @@ -149,32 +72,18 @@ class TaskPanel: def setupUi(self): mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") - form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") - form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") + form.points = self.widget(QtGui.QSpinBox, "Points") self.form = form - # Initial values if self.initValues(): return True self.retranslateUi() - # Connect Signals and Slots - QtCore.QObject.connect(form.trim, - QtCore.SIGNAL("valueChanged(double)"), - self.onData) - QtCore.QObject.connect(form.minDraft, - QtCore.SIGNAL("valueChanged(double)"), - self.onData) - QtCore.QObject.connect(form.maxDraft, - QtCore.SIGNAL("valueChanged(double)"), - self.onData) 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") + raise Exception("No main window found") def widget(self, class_id, name): """Return the selected widget. @@ -190,379 +99,89 @@ class TaskPanel: def initValues(self): """ Set initial values for fields """ - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") - form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") - form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") - selObjs = Gui.Selection.getSelection() if not selObjs: msg = QtGui.QApplication.translate( "ship_console", - "A ship instance must be selected before using this tool (no" + "A tank 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)): + for i in range(0, len(selObjs)): obj = selObjs[i] props = obj.PropertiesList try: - props.index("IsShip") + props.index("IsTank") except ValueError: continue - if obj.IsShip: - if self.ship: + if obj.IsTank: + if self.tank: msg = QtGui.QApplication.translate( "ship_console", - "More than one ship have been selected (the extra" - " ships will be ignored)", + "More than one tank have been selected (the extra" + " tanks will be ignored)", None, QtGui.QApplication.UnicodeUTF8) App.Console.PrintWarning(msg + '\n') break - self.ship = obj - - if not self.ship: + self.tank = obj + if not self.tank: msg = QtGui.QApplication.translate( "ship_console", - "A ship instance must be selected before using this tool (no" - " valid ship found at the selected objects)", + "A tank instance must be selected before using this tool (no" + " valid tank found at the selected objects)", None, QtGui.QApplication.UnicodeUTF8) App.Console.PrintError(msg + '\n') return True - - props = self.ship.PropertiesList - - length_format = USys.getLengthFormat() - angle_format = USys.getAngleFormat() - - try: - props.index("HydrostaticsTrim") - form.trim.setText(Locale.toString(angle_format.format( - self.ship.HydrostaticsTrim.getValueAs( - USys.getLengthUnits()).Value))) - except ValueError: - form.trim.setText(Locale.toString(angle_format.format(0.0))) - - try: - props.index("HydrostaticsMinDraft") - form.minDraft.setText(Locale.toString(length_format.format( - self.ship.HydrostaticsMinDraft.getValueAs( - USys.getLengthUnits()).Value))) - except ValueError: - form.minDraft.setText(Locale.toString(length_format.format( - 0.9 * self.ship.Draft.getValueAs(USys.getLengthUnits()).Value))) - try: - props.index("HydrostaticsMaxDraft") - form.maxDraft.setText(Locale.toString(length_format.format( - self.ship.HydrostaticsMaxDraft.getValueAs( - USys.getLengthUnits()).Value))) - except ValueError: - form.maxDraft.setText(Locale.toString(length_format.format( - 1.1 * self.ship.Draft.getValueAs(USys.getLengthUnits()).Value))) - - try: - props.index("HydrostaticsNDraft") - form.nDraft.setValue(self.ship.HydrostaticsNDraft) - except ValueError: - pass - return False def retranslateUi(self): - """ Set user interface locale strings. - """ + """ Set user interface locale strings. """ mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.setWindowTitle(QtGui.QApplication.translate( - "ship_hydrostatic", - "Plot hydrostatics", + "ship_capacity", + "Plot the tank capacity curve", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "TrimLabel").setText( + self.widget(QtGui.QLabel, "PointsLabel").setText( QtGui.QApplication.translate( - "ship_hydrostatic", - "Trim", - None, - QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "MinDraftLabel").setText( - QtGui.QApplication.translate( - "ship_hydrostatic", - "Minimum draft", - None, - QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "MaxDraftLabel").setText( - QtGui.QApplication.translate( - "ship_hydrostatic", - "Maximum draft", - None, - QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QLabel, "NDraftLabel").setText( - QtGui.QApplication.translate( - "ship_hydrostatic", + "ship_capacity", "Number of points", None, QtGui.QApplication.UnicodeUTF8)) - def clampLength(self, widget, val_min, val_max, val): - if val >= val_min and 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))) - return val - - def onData(self, value): - """ Method called when input data is changed. - @param value Changed value. - """ - if not self.ship: - return + def compute(self): mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") - form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") + form.points = self.widget(QtGui.QSpinBox, "Points") - # Get the values (or fix them in bad setting case) - try: - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value - except: - trim = 0.0 - input_format = USys.getAngleFormat() - qty = Units.Quantity('{} deg'.format(trim)) - form.trim.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) - try: - min_draft = Units.Quantity(Locale.fromString( - form.minDraft.text())).getValueAs('m').Value - except: - min_draft = 0.9 * self.ship.Draft.getValueAs('m').Value - input_format = USys.getLengthFormat() - qty = Units.Quantity('{} m'.format(min_draft)) - form.minDraft.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) - try: - max_draft = Units.Quantity(Locale.fromString( - form.minDraft.text())).getValueAs('m').Value - except: - max_draft = 0.9 * self.ship.Draft.getValueAs('m').Value - input_format = USys.getLengthFormat() - qty = Units.Quantity('{} m'.format(max_draft)) - form.maxDraft.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + bbox = self.tank.Shape.BoundBox + dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length) - # Clamp the values to the bounds - bbox = self.ship.Shape.BoundBox - draft_min = bbox.ZMin / Units.Metre.Value - draft_max = bbox.ZMax / Units.Metre.Value - min_draft = self.clampLength(form.minDraft, - draft_min, - draft_max, - min_draft) - max_draft = self.clampLength(form.maxDraft, - draft_min, - draft_max, - max_draft) - trim_min = -180.0 - trim_max = 180.0 - trim = self.clampAngle(form.trim, trim_min, trim_max, trim) + n = form.points.value() + dlevel = 100.0 / (n - 1) + l = [0.0] + v = [0.0] + z = [0.0] - # Clamp draft values to assert that the minimum value is lower than - # the maximum one - min_draft = self.clampLength(form.minDraft, - draft_min, - max_draft, - min_draft) - max_draft = self.clampLength(form.maxDraft, - min_draft, - draft_max, - max_draft) - - - def save(self): - """ Saves data into ship instance. - """ - mw = self.getMainWindow() - form = mw.findChild(QtGui.QWidget, "TaskPanel") - form.trim = self.widget(QtGui.QLineEdit, "Trim") - form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") - 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 - n_draft = form.nDraft.value() - - props = self.ship.PropertiesList - try: - props.index("HydrostaticsTrim") - except ValueError: - tooltip = str(QtGui.QApplication.translate( - "ship_hydrostatic", - "Hydrostatics tool trim selected", - None, - QtGui.QApplication.UnicodeUTF8)) - self.ship.addProperty("App::PropertyAngle", - "HydrostaticsTrim", - "Ship", - tooltip) - self.ship.HydrostaticsTrim = '{} deg'.format(trim) - - try: - props.index("HydrostaticsMinDraft") - except ValueError: - tooltip = str(QtGui.QApplication.translate( - "ship_hydrostatic", - "Hydrostatics tool minimum draft selected [m]", - None, - QtGui.QApplication.UnicodeUTF8)) - self.ship.addProperty("App::PropertyLength", - "HydrostaticsMinDraft", - "Ship", - tooltip) - self.ship.HydrostaticsMinDraft = '{} m'.format(min_draft) - - try: - props.index("HydrostaticsMaxDraft") - except ValueError: - tooltip = str(QtGui.QApplication.translate( - "ship_hydrostatic", - "Hydrostatics tool maximum draft selected [m]", - None, - QtGui.QApplication.UnicodeUTF8)) - self.ship.addProperty("App::PropertyLength", - "HydrostaticsMaxDraft", - "Ship", - tooltip) - self.ship.HydrostaticsMaxDraft = '{} m'.format(max_draft) - - try: - props.index("HydrostaticsNDraft") - except ValueError: - tooltip = str(QtGui.QApplication.translate( - "ship_hydrostatic", - "Hydrostatics tool number of points selected", - None, - QtGui.QApplication.UnicodeUTF8)) - self.ship.addProperty("App::PropertyInteger", - "HydrostaticsNDraft", - "Ship", - tooltip) - self.ship.HydrostaticsNDraft = form.nDraft.value() - - def lineFaceSection(self, line, surface): - """ Returns the point of section of a line with a face - @param line Line object, that can be a curve. - @param surface Surface object (must be a Part::Shape) - @return Section points array, [] if line don't cut surface - """ - result = [] - vertexes = line.Vertexes - nVertex = len(vertexes) - - section = line.cut(surface) - - points = section.Vertexes - return points - - def externalFaces(self, shape): - """ Returns detected external faces. - @param shape Shape where external faces wanted. - @return List of external faces detected. - """ - result = [] - faces = shape.Faces - bbox = shape.BoundBox - L = bbox.XMax - bbox.XMin - B = bbox.YMax - bbox.YMin - T = bbox.ZMax - bbox.ZMin - dist = math.sqrt(L*L + B*B + T*T) msg = QtGui.QApplication.translate( "ship_console", - "Computing external faces", + "Computing capacity curves", None, QtGui.QApplication.UnicodeUTF8) App.Console.PrintMessage(msg + '...\n') - # Valid/unvalid faces detection loop - for i in range(len(faces)): - App.Console.PrintMessage("\t{} / {}\n".format(i + 1, len(faces))) - f = faces[i] - # Create a line normal to surface at middle point - u = 0.0 - v = 0.0 - try: - surf = f.Surface - u = 0.5*(surf.getUKnots()[0]+surf.getUKnots()[-1]) - v = 0.5*(surf.getVKnots()[0]+surf.getVKnots()[-1]) - except: - cog = f.CenterOfMass - [u, v] = f.Surface.parameter(cog) - p0 = f.valueAt(u, v) - try: - n = f.normalAt(u, v).normalize() - except: - continue - p1 = p0 + n.multiply(1.5 * dist) - line = Part.makeLine(p0, p1) - # Look for faces in front of this - nPoints = 0 - for j in range(len(faces)): - f2 = faces[j] - section = self.lineFaceSection(line, f2) - if len(section) <= 2: - continue - # Add points discarding start and end - nPoints = nPoints + len(section) - 2 - # In order to avoid special directions we can modify line - # normal a little bit. - angle = 5 - line.rotate(p0, Vector(1, 0, 0), angle) - line.rotate(p0, Vector(0, 1, 0), angle) - line.rotate(p0, Vector(0, 0, 1), angle) - nPoints2 = 0 - for j in range(len(faces)): - if i == j: - continue - f2 = faces[j] - section = self.lineFaceSection(line, f2) - if len(section) <= 2: - continue - # Add points discarding start and end - nPoints2 = nPoints + len(section) - 2 - # If the number of intersection points is pair, is a - # external face. So if we found an odd points intersection, - # face must be discarded. - if (nPoints % 2) or (nPoints2 % 2): - continue - result.append(f) - self.timer.start(0.0) - self.loop.exec_() - if(not self.running): - break - return result + for i in range(1, n): + App.Console.PrintMessage("\t{} / {}\n".format(i + 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(): From 63f976e25ae10998dcfba06f3d9db6646a27453b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 18 Jan 2016 18:04:01 +0100 Subject: [PATCH 19/40] Implemented a draft of the GZ computation tool --- src/Mod/Ship/CMakeLists.txt | 1 + src/Mod/Ship/shipGZ/TaskPanel.py | 63 +++++--------- src/Mod/Ship/shipGZ/TaskPanel.ui | 12 +-- src/Mod/Ship/shipGZ/Tools.py | 143 +++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 53 deletions(-) create mode 100644 src/Mod/Ship/shipGZ/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index 10f63449c..efbc08b58 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -90,6 +90,7 @@ SET(ShipGZ_SRCS shipGZ/PlotAux.py shipGZ/TaskPanel.py shipGZ/TaskPanel.ui + shipGZ/Tools.py ) SOURCE_GROUP("shipgz" FILES ${ShipGZ_SRCS}) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index f54d7d428..ccc87cdfc 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -27,6 +27,7 @@ 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 @@ -40,6 +41,26 @@ class TaskPanel: 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") + + rolls = [] + roll = Units.Quantity(Locale.fromString( + form.angle.text())).getValueAs('deg').Value + n_points = form.n_points.value() + for i in range(n_points): + rolls.append(roll * i / float(n_points - 1)) + + gz = Tools.solve(self.ship, + self.weights, + self.tanks + rolls, + form.var_trim.isChecked()) + return True def reject(self): @@ -72,7 +93,6 @@ class TaskPanel: form.angle = self.widget(QtGui.QLineEdit, "Angle") form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") - form.var_draft = self.widget(QtGui.QCheckBox, "VariableDraft") form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") self.form = form if self.initValues(): @@ -278,7 +298,6 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.angle = self.widget(QtGui.QLineEdit, "Angle") form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") - form.var_draft = self.widget(QtGui.QCheckBox, "VariableDraft") form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") form.angle.setText(Locale.toString(angle_format.format(90.0))) # Try to use saved values @@ -295,14 +314,6 @@ class TaskPanel: form.n_points.setValue(self.ship.GZNumPoints) except ValueError: pass - try: - props.index("GZVariableDraft") - if self.ship.GZVariableDraft: - form.var_draft.setCheckState(QtCore.Qt.Checked) - else: - form.var_draft.setCheckState(QtCore.Qt.Unchecked) - except ValueError: - pass try: props.index("GZVariableTrim") if self.ship.GZVariableTrim: @@ -336,20 +347,6 @@ class TaskPanel: "Number of points", None, QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QCheckBox, "VariableDraft").setText( - QtGui.QApplication.translate( - "ship_gz", - "Variable draft", - None, - QtGui.QApplication.UnicodeUTF8)) - self.widget(QtGui.QCheckBox, "VariableDraft").setToolTip( - QtGui.QApplication.translate( - "ship_gz", - "The ship will be moved to the equilibrium draft for each" + \ - " roll angle. It will significantly increase the required" + \ - " computing time", - None, - QtGui.QApplication.UnicodeUTF8)) self.widget(QtGui.QCheckBox, "VariableTrim").setText( QtGui.QApplication.translate( "ship_gz", @@ -371,13 +368,11 @@ class TaskPanel: form = mw.findChild(QtGui.QWidget, "TaskPanel") form.angle = self.widget(QtGui.QLineEdit, "Angle") form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") - form.var_draft = self.widget(QtGui.QCheckBox, "VariableDraft") 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_draft = form.var_draft.isChecked() var_trim = form.var_trim.isChecked() props = self.ship.PropertiesList @@ -413,22 +408,6 @@ class TaskPanel: "Ship", tooltip) self.ship.GZNumPoints = n_points - try: - props.index("GZVariableDraft") - except ValueError: - try: - tooltip = str(QtGui.QApplication.translate( - "ship_areas", - "GZ curve tool variable draft selection", - None, - QtGui.QApplication.UnicodeUTF8)) - except: - tooltip = "GZ curve tool variable draft selection" - self.ship.addProperty("App::PropertyBool", - "GZVariableDraft", - "Ship", - tooltip) - self.ship.GZVariableDraft = var_draft try: props.index("GZVariableTrim") except ValueError: diff --git a/src/Mod/Ship/shipGZ/TaskPanel.ui b/src/Mod/Ship/shipGZ/TaskPanel.ui index 36fa605a3..b57157e5f 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.ui +++ b/src/Mod/Ship/shipGZ/TaskPanel.ui @@ -33,16 +33,6 @@ - - - - Variable Draft - - - true - - - @@ -59,7 +49,7 @@ - + Variable Trim angle diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py new file mode 100644 index 000000000..b025a1800 --- /dev/null +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -0,0 +1,143 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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 +from FreeCAD import Vector, Matrix, Placement +import Part +import Units +import FreeCAD as App +import FreeCADGui as Gui +import Instance as ShipInstance +import WeightInstance +import TankInstance +from shipHydrostatics import Tools as Hydrostatics + + +G = 9.81 +MAX_EQUILIBRIUM_ITERS = 10 +DENS = 1.025 # [tons/m3], salt water + + +def solve(ship, weights, tanks, rolls, var_trim=True): + """ Compute the ship GZ curve. + @param ship Ship instance. + @param weights Considered weights. + @param tanks Considered tanks. + @param rolls List of considered roll angles. + @param var_trim True if the trim angle should be recomputed at each roll + angle, False otherwise. + @return GZ values for each roll angle + """ + # Get the unloaded weight (ignoring the tanks for the moment). + W = 0.0 + COG = Vector() + for w in weights: + W += w.Proxy.getMass(w).getValueAs('kg').Value + m = w.Proxy.getMoment(w) + COG.x += m[0].getValueAs('kg*m').Value + COG.y += m[1].getValueAs('kg*m').Value + COG.z += m[2].getValueAs('kg*m').Value + COG = COG.multiply(1.0 / W) + W = W * G + + # Get the tanks weight + TW = 0.0 + for t in tanks: + # t[0] = tank object + # t[1] = load density + # t[2] = filling level + vol = t[0].Proxy.setFillingLevel(t[0], t[2]).getValueAs('m^3').Value + TW += vol * t[1] + TW = TW.getValueAs('kg').Value * G + + gzs = [] + for roll in rolls: + gz = solve_point(W, COG, TW, ship, tanks, roll, var_trim) + if gz is None: + return [] + gzs.append(solve_point(W, COG, TW, ship, tanks, roll, var_trim)) + + return gzs + +def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): + """ Compute the ship GZ value. + @param W Empty ship weight. + @param COG Empty ship Center of mass. + @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 + """ + gz = 0.0 + + # Look for the equilibrium draft (and eventually the trim angle too) + max_draft = ship.Shape.BoundBox.ZMax + draft = max_draft + max_disp = ship.Shape.Volume.getValueAs('m^3').Value * DENS * 1000.0 * G + if max_disp < W + TW: + msg = QtGui.QApplication.translate( + "ship_console", + "Too much weight! The ship will never displace water enough", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + ' ({} tons vs. {} tons)\n'.format( + max_disp / 1000.0 / G, (W + TW) / 1000.0 / G)) + return None + trim = 0.0 + for i in range(MAX_EQUILIBRIUM_ITERS): + # Get the displacement, and the bouyance application point + disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim) + disp *= 1000.0 * G + # Get the empty ship weight transformed application point + p = Part.makePoint(COG) + p.translate(Vector(0.0, 0.0, -draft)) + m = Matrix() + m.rotateX(math.radians(roll)) + m.rotateY(-math.radians(trim)) + p.rotate(Placement(m)) + # Add the tanks + # TODO + # --- + + # Compute the errors + draft_error = abs(disp - W - TW) / max_disp + if not var_trim: + trim_error = 0.0 + else: + dx = B.x - p.X + dz = B.z - p.Z + if abs(dx) < 0.001 * ship.Length.getValueAs('m').Value: + trim_error = 0.0 + else: + trim_error = math.degrees(math.atan2(dz, dx)) + + # Check if we can tolerate the errors + if draft_error < 0.01 and trim_error < 1.0: + break + + # Get the new draft and trim + draft += draft_error * max_draft + trim += 0.5 * trim_error + + return B.y - p.Y \ No newline at end of file From 8cc3e62589b20fc7986c36fe0fbb2f2e4c103fcd Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 19 Jan 2016 10:14:02 +0100 Subject: [PATCH 20/40] Improved the transversal areas computation --- src/Mod/Ship/shipHydrostatics/Tools.py | 72 +++++++++----------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 6f07aead4..c04327160 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -56,6 +56,8 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax 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 @@ -71,46 +73,13 @@ 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]) + areas.append([x / Units.Metre.Value, 0.0]) 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([x / Units.Metre.Value, f.Area / Units.Metre.Value**2]) # Last area is equal to zero (due to the total length usage) areas.append([xmax / Units.Metre.Value, 0.0]) App.Console.PrintMessage("Done!\n") @@ -144,15 +113,16 @@ def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax # 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) + B = ymax - ymin + p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: - box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p) + box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) except Part.OCCError: return [0.0, Vector(), 0.0] - vol = 0.0 cog = Vector() for solid in shape.Solids: @@ -212,11 +182,13 @@ def wettedArea(shape, draft, trim): bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax # Create the "sea" box L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) + B = ymax - ymin + p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) except Part.OCCError: @@ -273,11 +245,13 @@ def FloatingArea(ship, draft, trim): bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax # Create the "sea" box L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) + B = ymax - ymin + p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) except Part.OCCError: @@ -370,11 +344,13 @@ def mainFrameCoeff(ship, draft): bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax + ymin = bbox.YMin + ymax = bbox.YMax # Create the "sea" box L = xmax - xmin - B = bbox.YMax - bbox.YMin - p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) + B = ymax - ymin + p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: box = Part.makeBox(1.5 * L, 3.0 * B, - bbox.ZMin + 1.0, p) except Part.OCCError: @@ -463,4 +439,4 @@ class Point: self.BMt = bm self.Cb = dispData[2] self.Cf = farea[1] - self.Cm = cm + self.Cm = cm \ No newline at end of file From a5d7344f5aed7f5ae3e53c78c2b49de197e56de3 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 19 Jan 2016 11:16:36 +0100 Subject: [PATCH 21/40] Modified the ship shpae transformation process during the hydrostatics computation --- src/Mod/Ship/shipHydrostatics/Tools.py | 109 ++++++++++++++++--------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index c04327160..035b3dbf3 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -22,7 +22,7 @@ #*************************************************************************** import math -from FreeCAD import Vector +from FreeCAD import Vector, Matrix, Placement import Part import Units import FreeCAD as App @@ -48,10 +48,23 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): # 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)) + _draft = draft * Units.Metre.Value + # 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 and yaw 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.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) + shape.translate(Vector(0.0, 0.0, -_draft)) # Sections distance computation bbox = shape.BoundBox xmin = bbox.XMin @@ -104,65 +117,81 @@ def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): # 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)) + _draft = draft * Units.Metre.Value + # 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 and yaw 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.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) + shape.translate(Vector(0.0, 0.0, -_draft)) + Part.show(shape) + ship_shape = 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 - p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) + H = zmax - zmin + p = Vector(xmin - L, ymin - B, zmin - H) try: - box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) + box = Part.makeBox(3.0 * L, 3.0 * B, - zmin + H, p) except Part.OCCError: return [0.0, Vector(), 0.0] + Part.show(box) + box_shape = App.ActiveDocument.Objects[-1] + + common = App.activeDocument().addObject("Part::MultiCommon", + "DisplacementHelper") + common.Shapes = [ship_shape, box_shape] + App.ActiveDocument.recompute() + 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 + for solid in common.Shape.Solids: + vol += solid.Volume / Units.Metre.Value**3 + sCoG = solid.CenterOfMass + cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4 + cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4 + cog.z = cog.z + sCoG.z * solid.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 + + App.ActiveDocument.removeObject(common.Name) + App.ActiveDocument.removeObject(ship_shape.Name) + App.ActiveDocument.removeObject(box_shape.Name) + # 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 + B = Part.Point(Vector(cog.x, cog.y, cog.z)) + m = Matrix() + m.move(Vector(0.0, 0.0, _draft)) + m.rotateZ(-math.radians(yaw)) + m.move(Vector(-_draft * math.sin(math.radians(roll)), 0.0, 0.0)) + m.rotateY(math.radians(trim)) + m.move(Vector(0.0, -_draft * math.sin(math.radians(trim)), base_z)) + m.rotateX(-math.radians(roll)) + B.transform(m) + # Return the computed data dens = 1.025 # [tons/m3], salt water - return [dens*vol, B, vol/Vol] + return [dens*vol, Vector(B.X, B.Y, B.Z), vol/Vol] def wettedArea(shape, draft, trim): @@ -176,8 +205,9 @@ def wettedArea(shape, draft, trim): nObjects = 0 shape = shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) + _draft = draft * Units.Metre.Value shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) + shape.translate(Vector(0.0, 0.0, -_draft)) bbox = shape.BoundBox xmin = bbox.XMin @@ -239,8 +269,9 @@ def FloatingArea(ship, draft, trim): minY = 0.0 shape = ship.Shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) + _draft = draft * Units.Metre.Value shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) + shape.translate(Vector(0.0, 0.0, -_draft)) bbox = shape.BoundBox xmin = bbox.XMin From 3644dcbcabf0f0728d22ec6e71def044ff58db06 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Tue, 19 Jan 2016 13:16:32 +0100 Subject: [PATCH 22/40] Added a much more robust displacement tool for the GZ computation --- src/Mod/Ship/shipGZ/TaskPanel.py | 2 +- src/Mod/Ship/shipGZ/Tools.py | 47 ++++++++-------- src/Mod/Ship/shipHydrostatics/Tools.py | 77 ++++++++++++++++++-------- 3 files changed, 77 insertions(+), 49 deletions(-) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index ccc87cdfc..496b5b516 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -57,7 +57,7 @@ class TaskPanel: gz = Tools.solve(self.ship, self.weights, - self.tanks + self.tanks, rolls, form.var_trim.isChecked()) diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index b025a1800..ecb6d6313 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -36,6 +36,7 @@ from shipHydrostatics import Tools as Hydrostatics G = 9.81 MAX_EQUILIBRIUM_ITERS = 10 DENS = 1.025 # [tons/m3], salt water +TRIM_RELAX_FACTOR = 10.0 def solve(ship, weights, tanks, rolls, var_trim=True): @@ -68,14 +69,15 @@ def solve(ship, weights, tanks, rolls, var_trim=True): # t[2] = filling level vol = t[0].Proxy.setFillingLevel(t[0], t[2]).getValueAs('m^3').Value TW += vol * t[1] - TW = TW.getValueAs('kg').Value * G + TW = TW * G gzs = [] - for roll in rolls: + for i,roll in enumerate(rolls): + App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls))) gz = solve_point(W, COG, TW, ship, tanks, roll, var_trim) if gz is None: return [] - gzs.append(solve_point(W, COG, TW, ship, tanks, roll, var_trim)) + gzs.append(gz) return gzs @@ -92,9 +94,9 @@ def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): gz = 0.0 # Look for the equilibrium draft (and eventually the trim angle too) - max_draft = ship.Shape.BoundBox.ZMax - draft = max_draft - max_disp = ship.Shape.Volume.getValueAs('m^3').Value * DENS * 1000.0 * G + max_draft = ship.Shape.BoundBox.ZMax / Units.Metre.Value + draft = ship.Draft.getValueAs('m').Value + max_disp = ship.Shape.Volume / Units.Metre.Value**3 * DENS * 1000.0 * G if max_disp < W + TW: msg = QtGui.QApplication.translate( "ship_console", @@ -109,35 +111,32 @@ def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): # Get the displacement, and the bouyance application point disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim) disp *= 1000.0 * G - # Get the empty ship weight transformed application point - p = Part.makePoint(COG) - p.translate(Vector(0.0, 0.0, -draft)) - m = Matrix() - m.rotateX(math.radians(roll)) - m.rotateY(-math.radians(trim)) - p.rotate(Placement(m)) - # Add the tanks + # Add the tanks effect on the center of gravity # TODO # --- # Compute the errors - draft_error = abs(disp - W - TW) / max_disp + draft_error = -(disp - W - TW) / max_disp + R = COG - B if not var_trim: trim_error = 0.0 else: - dx = B.x - p.X - dz = B.z - p.Z - if abs(dx) < 0.001 * ship.Length.getValueAs('m').Value: - trim_error = 0.0 - else: - trim_error = math.degrees(math.atan2(dz, dx)) + trim_error = -TRIM_RELAX_FACTOR * R.x / ship.Length.getValueAs('m').Value # Check if we can tolerate the errors - if draft_error < 0.01 and trim_error < 1.0: + if abs(draft_error) < 0.01 and abs(trim_error) < 0.05: break # Get the new draft and trim draft += draft_error * max_draft - trim += 0.5 * trim_error + trim += trim_error - return B.y - p.Y \ No newline at end of file + App.Console.PrintMessage("draft and trim: {}, {}\n".format(draft, trim)) + + App.Console.PrintMessage(R) + App.Console.PrintMessage("\n") + c = math.cos(math.radians(roll)) + s = math.sin(math.radians(roll)) + App.Console.PrintMessage(c * R.y - s * R.z) + App.Console.PrintMessage("\n") + return c * R.y - s * R.z \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 035b3dbf3..0561353f7 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -22,13 +22,20 @@ #*************************************************************************** import math -from FreeCAD import Vector, Matrix, Placement +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 + + +DENS = 1.025 # [tons/m3], salt water +COMMON_BOOLEAN_ITERATIONS = 10 def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): @@ -148,50 +155,72 @@ def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): L = xmax - xmin B = ymax - ymin H = zmax - zmin - p = Vector(xmin - L, ymin - B, zmin - H) - try: - box = Part.makeBox(3.0 * L, 3.0 * B, - zmin + H, p) - except Part.OCCError: - return [0.0, Vector(), 0.0] - Part.show(box) - box_shape = App.ActiveDocument.Objects[-1] + 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", "DisplacementHelper") - common.Shapes = [ship_shape, box_shape] + common.Shapes = [ship_shape, 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", + "Boolean operation failed. The tool is retrying that 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() vol = 0.0 cog = Vector() - for solid in common.Shape.Solids: - vol += solid.Volume / Units.Metre.Value**3 - sCoG = solid.CenterOfMass - cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4 - cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4 - cog.z = cog.z + sCoG.z * solid.Volume / Units.Metre.Value**4 - cog.x = cog.x / vol - cog.y = cog.y / vol - cog.z = cog.z / vol + if len(common.Shape.Solids) > 0: + for solid in common.Shape.Solids: + vol += solid.Volume / Units.Metre.Value**3 + sCoG = solid.CenterOfMass + cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4 + cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4 + cog.z = cog.z + sCoG.z * solid.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 App.ActiveDocument.removeObject(common.Name) App.ActiveDocument.removeObject(ship_shape.Name) - App.ActiveDocument.removeObject(box_shape.Name) + App.ActiveDocument.removeObject(box.Name) + App.ActiveDocument.recompute() # Undo the transformations B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() - m.move(Vector(0.0, 0.0, _draft)) + m.move(Vector(0.0, 0.0, draft)) m.rotateZ(-math.radians(yaw)) - m.move(Vector(-_draft * math.sin(math.radians(roll)), 0.0, 0.0)) + m.move(Vector(-draft * math.sin(math.radians(roll)), 0.0, 0.0)) m.rotateY(math.radians(trim)) - m.move(Vector(0.0, -_draft * math.sin(math.radians(trim)), base_z)) + m.move(Vector(0.0, + -draft * math.sin(math.radians(trim)), + base_z / Units.Metre.Value)) m.rotateX(-math.radians(roll)) B.transform(m) # Return the computed data - dens = 1.025 # [tons/m3], salt water - return [dens*vol, Vector(B.X, B.Y, B.Z), vol/Vol] + return [DENS*vol, Vector(B.X, B.Y, B.Z), vol/Vol] def wettedArea(shape, draft, trim): From 65df5010f8e495f092a5843e8431ecc29b5589ba Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Wed, 20 Jan 2016 11:51:59 +0100 Subject: [PATCH 23/40] Fixed a typo in the transformation angles --- src/Mod/Ship/shipHydrostatics/Tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 0561353f7..a627d617f 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -211,10 +211,10 @@ def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): m = Matrix() m.move(Vector(0.0, 0.0, draft)) m.rotateZ(-math.radians(yaw)) - m.move(Vector(-draft * math.sin(math.radians(roll)), 0.0, 0.0)) + m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0)) m.rotateY(math.radians(trim)) m.move(Vector(0.0, - -draft * math.sin(math.radians(trim)), + -draft * math.sin(math.radians(roll)), base_z / Units.Metre.Value)) m.rotateX(-math.radians(roll)) B.transform(m) From 281456585065b74eac5086ec857646e2156df664 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Wed, 20 Jan 2016 11:52:36 +0100 Subject: [PATCH 24/40] Cleared out some debug messages --- src/Mod/Ship/shipGZ/Tools.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index ecb6d6313..2c4a056db 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -133,10 +133,7 @@ def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): App.Console.PrintMessage("draft and trim: {}, {}\n".format(draft, trim)) - App.Console.PrintMessage(R) - App.Console.PrintMessage("\n") + # GZ should be provided in the Free surface oriented frame of reference c = math.cos(math.radians(roll)) s = math.sin(math.radians(roll)) - App.Console.PrintMessage(c * R.y - s * R.z) - App.Console.PrintMessage("\n") return c * R.y - s * R.z \ No newline at end of file From d8e511f1dc47cd18882a390be1a2b9831880156b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Wed, 20 Jan 2016 12:35:47 +0100 Subject: [PATCH 25/40] Moved to a more robust Part module based boolean common operator --- src/Mod/Ship/TankInstance.py | 113 +++++++++----------- src/Mod/Ship/shipCapacityCurve/TaskPanel.py | 8 +- src/Mod/Ship/shipGZ/Tools.py | 2 +- 3 files changed, 57 insertions(+), 66 deletions(-) diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index e50831e38..aa2dafcd0 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -24,12 +24,16 @@ import time from math import * from PySide import QtGui, QtCore -import FreeCAD -import FreeCADGui -from FreeCAD import Base, Vector +import FreeCAD as App +import FreeCADGui as Gui +from FreeCAD import Base, Vector, Placement, Rotation import Part import Units from shipUtils import Paths, Math +import shipUtils.Units as USys + + +COMMON_BOOLEAN_ITERATIONS = 10 class Tank: @@ -52,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) @@ -97,52 +79,61 @@ 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 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]). """ - shape = fp.Shape - solids = shape.Solids + max(min(level, 1.0), 0.0) - # 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 - vol = 0.0 - for s in solids: - try: - fluid = s.common(box) - v = fluid.Volume - except: - v = 0.0 - vol += v + box = App.ActiveDocument.addObject("Part::Box","Box") + length_format = USys.getLengthFormat() + box.Placement = Placement(Vector(bbox.XMin - dx, + bbox.XMin - 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) - # Get the volume quantity and store it with the right units - vol = Units.Quantity(vol, Units.Volume) - fp.Vol = vol.getValueAs("m^3").Value + # Create a new object on top of a copy of the tank shape + Part.show(fp.Shape.copy()) + tank = App.ActiveDocument.Objects[-1] - return vol + # 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() + + return Units.Quantity(common.Shape.Volume, Units.Volume) class ViewProviderTank: diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index c4c61ab90..1faf79325 100644 --- a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py +++ b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py @@ -163,7 +163,7 @@ class TaskPanel: dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length) n = form.points.value() - dlevel = 100.0 / (n - 1) + dlevel = 1.0 / (n - 1) l = [0.0] v = [0.0] z = [0.0] @@ -177,9 +177,9 @@ class TaskPanel: for i in range(1, n): App.Console.PrintMessage("\t{} / {}\n".format(i + 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) + vol = self.tank.Proxy.getVolume(self.tank, level) + l.append(level * 100.0) + z.append(level * dz.getValueAs("m").Value) v.append(vol.getValueAs("m^3").Value) return (l, z, v) diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index 2c4a056db..c48514551 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -67,7 +67,7 @@ def solve(ship, weights, tanks, rolls, var_trim=True): # t[0] = tank object # t[1] = load density # t[2] = filling level - vol = t[0].Proxy.setFillingLevel(t[0], t[2]).getValueAs('m^3').Value + vol = t[0].Proxy.getVolume(t[0], t[2]).getValueAs('m^3').Value TW += vol * t[1] TW = TW * G From 6315084f58cfb5e7062d0f6258d532ea13330492 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Wed, 20 Jan 2016 12:36:05 +0100 Subject: [PATCH 26/40] Slightly improved the capacity curve plot --- src/Mod/Ship/shipCapacityCurve/PlotAux.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/shipCapacityCurve/PlotAux.py b/src/Mod/Ship/shipCapacityCurve/PlotAux.py index 81e0595b8..12d02b00a 100644 --- a/src/Mod/Ship/shipCapacityCurve/PlotAux.py +++ b/src/Mod/Ship/shipCapacityCurve/PlotAux.py @@ -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 @@ -86,7 +93,7 @@ class Plot(object): 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, 1.0)) From 9770cca561854c7f9d0d7e75b29863f9956fd88b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Wed, 20 Jan 2016 15:13:00 +0100 Subject: [PATCH 27/40] Finished the GZ curve implementation --- src/Mod/Ship/TankInstance.py | 103 +++++++++++++++++++++-- src/Mod/Ship/shipGZ/PlotAux.py | 139 +++++++++++-------------------- src/Mod/Ship/shipGZ/TaskPanel.py | 12 +-- src/Mod/Ship/shipGZ/Tools.py | 37 +++++--- 4 files changed, 178 insertions(+), 113 deletions(-) diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index aa2dafcd0..038e6e918 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -26,7 +26,7 @@ from math import * from PySide import QtGui, QtCore import FreeCAD as App import FreeCADGui as Gui -from FreeCAD import Base, Vector, Placement, Rotation +from FreeCAD import Base, Vector, Matrix, Placement, Rotation import Part import Units from shipUtils import Paths, Math @@ -79,14 +79,23 @@ class Tank: """ pass - def getVolume(self, fp, level): + 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 (interval [0, 1]). + return_shape -- False if the tool should return the fluid volume value, + True if the tool should return the volume shape. """ - max(min(level, 1.0), 0.0) + 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) # Build up the cutting box bbox = fp.Shape.BoundBox @@ -97,7 +106,7 @@ class Tank: box = App.ActiveDocument.addObject("Part::Box","Box") length_format = USys.getLengthFormat() box.Placement = Placement(Vector(bbox.XMin - dx, - bbox.XMin - dy, + bbox.YMin - dy, bbox.ZMin - dz), Rotation(App.Vector(0,0,1),0)) box.Length = length_format.format(3.0 * dx) @@ -133,7 +142,91 @@ class Tank: random_bounds)) App.ActiveDocument.recompute() - return Units.Quantity(common.Shape.Volume, Units.Volume) + 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=0.0, trim=0.0): + """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 (in m^3). + roll -- Ship roll angle (in degrees). + trim -- Ship trim angle (in degrees). + + If the fluid volume is bigger than the total tank one, it will be + conveniently clamped. + """ + # Change the units of the volume, and clamp the value + vol = vol * Units.Metre.Value**3 + if vol <= 0.0: + return Vector() + if vol >= fp.Shape.Volume: + 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 / fp.Shape.Volume + + # Transform the tank shape + current_placement = fp.Placement + m = current_placement.toMatrix() + m.rotateX(radians(roll)) + m.rotateY(-radians(trim)) + fp.Placement = m + + # Iterate to find the fluid shape + for i in range(COMMON_BOOLEAN_ITERATIONS): + shape = self.getVolume(fp, level, return_shape=True) + error = (vol - shape.Volume) / fp.Shape.Volume + if abs(error) < 0.01: + break + level += error + + # Get the center of gravity + vol = 0.0 + 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 + + # Untransform the object to retrieve the original position + fp.Placement = current_placement + p = Part.Point(cog) + m = Matrix() + m.rotateY(radians(trim)) + m.rotateX(-radians(roll)) + p.rotate(Placement(m)) + + return Vector(p.X, p.Y, p.Z) class ViewProviderTank: diff --git a/src/Mod/Ship/shipGZ/PlotAux.py b/src/Mod/Ship/shipGZ/PlotAux.py index 4d6427a71..cbbf56879 100644 --- a/src/Mod/Ship/shipGZ/PlotAux.py +++ b/src/Mod/Ship/shipGZ/PlotAux.py @@ -22,36 +22,37 @@ #*************************************************************************** import os +import math from PySide import QtGui, QtCore import FreeCAD import FreeCADGui -from FreeCAD import Base import Spreadsheet +from shipUtils import Paths + class Plot(object): - def __init__(self, x, y, disp, xcb, ship): - """ Constructor. performs the plot and shows it. - @param x X coordinates. - @param y Transversal computed areas. - @param disp Ship displacement. - @param xcb Bouyancy center length. - @param ship Active ship instance. - """ - self.plot(x, y, disp, xcb, ship) - self.spreadSheet(x, y, ship) + def __init__(self, roll, gz, draft, trim): + """ Plot the GZ curve - def plot(self, x, y, disp, xcb, ship): - """ Perform the areas curve plot. - @param x X coordinates. - @param y Transversal areas. - @param disp Ship displacement. - @param xcb Bouyancy center length. - @param ship Active ship instance. - @return True if error happens. + 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('Areas curve') + plt = Plot.figure('GZ') except ImportError: msg = QtGui.QApplication.translate( "ship_console", @@ -60,90 +61,46 @@ class Plot(object): QtGui.QApplication.UnicodeUTF8) FreeCAD.Console.PrintWarning(msg + '\n') return True - # Plot areas curve - areas = Plot.plot(x, y, 'Transversal areas') - areas.line.set_linestyle('-') - areas.line.set_linewidth(2.0) - areas.line.set_color((0.0, 0.0, 0.0)) - # Get perpendiculars data - Lpp = ship.Length.getValueAs('m').Value - FPx = 0.5 * Lpp - APx = -0.5 * Lpp - maxArea = max(y) - # Plot perpendiculars - FP = Plot.plot([FPx, FPx], [0.0, maxArea]) - FP.line.set_linestyle('-') - FP.line.set_linewidth(1.0) - FP.line.set_color((0.0, 0.0, 0.0)) - AP = Plot.plot([APx, APx], [0.0, maxArea]) - AP.line.set_linestyle('-') - AP.line.set_linewidth(1.0) - AP.line.set_color((0.0, 0.0, 0.0)) - # Add annotations for prependiculars + + 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() - ax.annotate('AP', xy=(APx + 0.01 * Lpp, 0.01 * maxArea), size=15) - ax.annotate('AP', xy=(APx + 0.01 * Lpp, 0.95 * maxArea), size=15) - ax.annotate('FP', xy=(FPx + 0.01 * Lpp, 0.01 * maxArea), size=15) - ax.annotate('FP', xy=(FPx + 0.01 * Lpp, 0.95 * maxArea), size=15) - # Add some additional data - addInfo = ("$XCB = {0} \\; \\mathrm{{m}}$\n" - "$Area_{{max}} = {1} \\; \\mathrm{{m}}^2$\n" - "$\\bigtriangleup = {2} \\; \\mathrm{{tons}}$".format( - xcb, - maxArea, - disp)) - ax.text(0.0, - 0.01 * maxArea, - addInfo, - verticalalignment='bottom', - horizontalalignment='center', - fontsize=20) - # Write axes titles - Plot.xlabel(r'$x \; \mathrm{m}$') - Plot.ylabel(r'$Area \; \mathrm{m}^2$') + Plot.xlabel(r'$\phi \; [\mathrm{deg}]$') + Plot.ylabel(r'$GZ \; [\mathrm{m}]$') ax.xaxis.label.set_fontsize(20) ax.yaxis.label.set_fontsize(20) - # Show grid + Plot.grid(True) - # End plt.update() return False - def spreadSheet(self, x, y, ship): - """ Write the output data file. - @param x X coordinates. - @param y Transversal areas. - @param ship Active ship instance. + 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', - 'Areas curve') + 'GZ') # Print the header - s.set("A1", "x [m]") - s.set("B1", "area [m^2]") - s.set("C1", "FP x") - s.set("D1", "FP y") - s.set("E1", "AP x") - s.set("F1", "AP y") + s.set("A1", "roll [deg]") + s.set("B1", "GZ [m]") + s.set("C1", "draft [m]") + s.set("D1", "trim [deg]") - # Print the perpendiculars data - Lpp = ship.Length.getValueAs('m').Value - FPx = 0.5 * Lpp - APx = -0.5 * Lpp - maxArea = max(y) - s.set("C2", str(FPx)) - s.set("D2", str(0.0)) - s.set("C3", str(FPx)) - s.set("D3", str(maxArea)) - s.set("E2", str(APx)) - s.set("F2", str(0.0)) - s.set("E3", str(APx)) - s.set("F3", str(maxArea)) - # Print the data - for i in range(len(x)): - s.set("A{}".format(i + 2), str(x[i])) - s.set("B{}".format(i + 2), str(y[i])) + 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 index 496b5b516..64173cd97 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -55,11 +55,13 @@ class TaskPanel: for i in range(n_points): rolls.append(roll * i / float(n_points - 1)) - gz = Tools.solve(self.ship, - self.weights, - self.tanks, - rolls, - form.var_trim.isChecked()) + gzs, drafts, trims = Tools.solve(self.ship, + self.weights, + self.tanks, + rolls, + form.var_trim.isChecked()) + + PlotAux.Plot(rolls, gzs, drafts, trims) return True diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index c48514551..cf5d3b230 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -47,7 +47,8 @@ def solve(ship, weights, tanks, rolls, var_trim=True): @param rolls List of considered roll angles. @param var_trim True if the trim angle should be recomputed at each roll angle, False otherwise. - @return GZ values for each roll angle + @return GZ values, drafts and trim angles, for each roll angle (in 3 + separated lists) """ # Get the unloaded weight (ignoring the tanks for the moment). W = 0.0 @@ -63,33 +64,43 @@ def solve(ship, weights, tanks, rolls, var_trim=True): # Get the tanks weight TW = 0.0 + VOLS = [] for t in tanks: # t[0] = tank object # t[1] = load density # t[2] = filling level vol = t[0].Proxy.getVolume(t[0], t[2]).getValueAs('m^3').Value + VOLS.append(vol) TW += vol * t[1] TW = TW * G gzs = [] + drafts = [] + trims = [] for i,roll in enumerate(rolls): App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls))) - gz = solve_point(W, COG, TW, ship, tanks, roll, var_trim) + gz, draft, trim = solve_point(W, COG, TW, VOLS, + ship, tanks, roll, var_trim) if gz is None: return [] gzs.append(gz) + drafts.append(draft) + trims.append(trim) - return gzs + return gzs, drafts, trims -def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): +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 + @return GZ value, equilibrium draft, and equilibrium trim angle (0 if + variable trim has not been requested) """ gz = 0.0 @@ -107,17 +118,21 @@ def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): max_disp / 1000.0 / G, (W + TW) / 1000.0 / G)) return None trim = 0.0 + for i in range(MAX_EQUILIBRIUM_ITERS): # Get the displacement, and the bouyance application point disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim) disp *= 1000.0 * G # Add the tanks effect on the center of gravity - # TODO - # --- - + cog = Vector(COG.x * W, COG.y * W, COG.z * W) + for i,t in enumerate(tanks): + tank_weight = VOLS[i] * t[1] * G + cog += t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim).multiply( + tank_weight / Units.Metre.Value) + cog = cog.multiply(1.0 / (W + TW)) # Compute the errors draft_error = -(disp - W - TW) / max_disp - R = COG - B + R = cog - B if not var_trim: trim_error = 0.0 else: @@ -131,9 +146,7 @@ def solve_point(W, COG, TW, ship, tanks, roll, var_trim=True): draft += draft_error * max_draft trim += trim_error - App.Console.PrintMessage("draft and trim: {}, {}\n".format(draft, trim)) - # GZ should be provided in the Free surface oriented frame of reference c = math.cos(math.radians(roll)) s = math.sin(math.radians(roll)) - return c * R.y - s * R.z \ No newline at end of file + return c * R.y - s * R.z, draft, trim \ No newline at end of file From 495308905cd15b0d2ace18776f7e6860b50bb69f Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Thu, 21 Jan 2016 18:46:08 +0100 Subject: [PATCH 28/40] Added a console interface to create the ship instance --- src/Mod/Ship/CMakeLists.txt | 1 + src/Mod/Ship/Ship.py | 32 ++++++++++++ src/Mod/Ship/shipCreateShip/TaskPanel.py | 12 ++--- src/Mod/Ship/shipCreateShip/Tools.py | 65 ++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 src/Mod/Ship/Ship.py create mode 100644 src/Mod/Ship/shipCreateShip/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index efbc08b58..c44c5e7e9 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}) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py new file mode 100644 index 000000000..1dbb91a1a --- /dev/null +++ b/src/Mod/Ship/Ship.py @@ -0,0 +1,32 @@ +#*************************************************************************** +#* * +#* 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 \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateShip/TaskPanel.py b/src/Mod/Ship/shipCreateShip/TaskPanel.py index f3084507a..38181f313 100644 --- a/src/Mod/Ship/shipCreateShip/TaskPanel.py +++ b/src/Mod/Ship/shipCreateShip/TaskPanel.py @@ -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..eb610fbac --- /dev/null +++ b/src/Mod/Ship/shipCreateShip/Tools.py @@ -0,0 +1,65 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011, 2012 * +#* 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 From 94cc4b4e36952142e7394f68b6a30fdfcccf9741 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Thu, 21 Jan 2016 18:56:28 +0100 Subject: [PATCH 29/40] Updated the copyright notifications --- src/Mod/Ship/InitGui.py | 2 +- src/Mod/Ship/Instance.py | 2 +- src/Mod/Ship/ShipGui.py | 2 +- src/Mod/Ship/TankInstance.py | 2 +- src/Mod/Ship/WeightInstance.py | 2 +- src/Mod/Ship/shipAreasCurve/PlotAux.py | 2 +- src/Mod/Ship/shipAreasCurve/Preview.py | 2 +- src/Mod/Ship/shipAreasCurve/TaskPanel.py | 2 +- src/Mod/Ship/shipAreasCurve/__init__.py | 2 +- src/Mod/Ship/shipCapacityCurve/PlotAux.py | 2 +- src/Mod/Ship/shipCapacityCurve/TaskPanel.py | 2 +- src/Mod/Ship/shipCapacityCurve/__init__.py | 2 +- src/Mod/Ship/shipCreateLoadCondition/__init__.py | 2 +- src/Mod/Ship/shipCreateShip/Preview.py | 2 +- src/Mod/Ship/shipCreateShip/TaskPanel.py | 2 +- src/Mod/Ship/shipCreateShip/Tools.py | 2 +- src/Mod/Ship/shipCreateShip/__init__.py | 2 +- src/Mod/Ship/shipCreateTank/TaskPanel.py | 2 +- src/Mod/Ship/shipCreateTank/__init__.py | 2 +- src/Mod/Ship/shipCreateWeight/TaskPanel.py | 2 +- src/Mod/Ship/shipCreateWeight/__init__.py | 2 +- src/Mod/Ship/shipGZ/PlotAux.py | 2 +- src/Mod/Ship/shipGZ/TaskPanel.py | 2 +- src/Mod/Ship/shipGZ/Tools.py | 2 +- src/Mod/Ship/shipGZ/__init__.py | 2 +- src/Mod/Ship/shipHydrostatics/PlotAux.py | 2 +- src/Mod/Ship/shipHydrostatics/TaskPanel.py | 2 +- src/Mod/Ship/shipHydrostatics/Tools.py | 4 ++-- src/Mod/Ship/shipHydrostatics/__init__.py | 2 +- src/Mod/Ship/shipLoadExample/TaskPanel.py | 2 +- src/Mod/Ship/shipLoadExample/__init__.py | 2 +- src/Mod/Ship/shipOutlineDraw/Preview.py | 2 +- src/Mod/Ship/shipOutlineDraw/TaskPanel.py | 2 +- src/Mod/Ship/shipOutlineDraw/__init__.py | 2 +- src/Mod/Ship/shipUtils/Locale.py | 2 +- src/Mod/Ship/shipUtils/Math.py | 2 +- src/Mod/Ship/shipUtils/Paths.py | 2 +- src/Mod/Ship/shipUtils/Units.py | 2 +- src/Mod/Ship/shipUtils/__init__.py | 2 +- 39 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Mod/Ship/InitGui.py b/src/Mod/Ship/InitGui.py index 04ffd62fa..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 * diff --git a/src/Mod/Ship/Instance.py b/src/Mod/Ship/Instance.py index f22eda010..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 * diff --git a/src/Mod/Ship/ShipGui.py b/src/Mod/Ship/ShipGui.py index 881157c2c..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 * diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index 038e6e918..c6bb90ccf 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 * diff --git a/src/Mod/Ship/WeightInstance.py b/src/Mod/Ship/WeightInstance.py index 746e5f136..c524c85db 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 * diff --git a/src/Mod/Ship/shipAreasCurve/PlotAux.py b/src/Mod/Ship/shipAreasCurve/PlotAux.py index 4d6427a71..e83578e28 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 * diff --git a/src/Mod/Ship/shipAreasCurve/Preview.py b/src/Mod/Ship/shipAreasCurve/Preview.py index a7428d5e8..5a13718b8 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 * diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.py b/src/Mod/Ship/shipAreasCurve/TaskPanel.py index 40719b935..83b45da75 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 * 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 12d02b00a..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 * diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index 1faf79325..1118f22e7 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 * 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/__init__.py b/src/Mod/Ship/shipCreateLoadCondition/__init__.py index 4a06fba41..b33214bd5 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__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/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 38181f313..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 * diff --git a/src/Mod/Ship/shipCreateShip/Tools.py b/src/Mod/Ship/shipCreateShip/Tools.py index eb610fbac..58403689c 100644 --- a/src/Mod/Ship/shipCreateShip/Tools.py +++ b/src/Mod/Ship/shipCreateShip/Tools.py @@ -1,6 +1,6 @@ #*************************************************************************** #* * -#* Copyright (c) 2011, 2012 * +#* Copyright (c) 2016 * #* Jose Luis Cercos Pita * #* * #* This program is free software; you can redistribute it and/or modify * 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 c060ba914..01a141709 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 * 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 6af196eb9..cd8a312a5 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 * 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 index cbbf56879..08da5e1dc 100644 --- a/src/Mod/Ship/shipGZ/PlotAux.py +++ b/src/Mod/Ship/shipGZ/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 * diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 64173cd97..0a4110e8b 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/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/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index cf5d3b230..6c91a54d4 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/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 * diff --git a/src/Mod/Ship/shipGZ/__init__.py b/src/Mod/Ship/shipGZ/__init__.py index 812df3a4a..c8f2b6eba 100644 --- a/src/Mod/Ship/shipGZ/__init__.py +++ b/src/Mod/Ship/shipGZ/__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/shipHydrostatics/PlotAux.py b/src/Mod/Ship/shipHydrostatics/PlotAux.py index c911b1822..532d16f30 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 * diff --git a/src/Mod/Ship/shipHydrostatics/TaskPanel.py b/src/Mod/Ship/shipHydrostatics/TaskPanel.py index 100a9278d..f5c9d8107 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 * diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index a627d617f..628369ff2 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 * @@ -499,4 +499,4 @@ class Point: self.BMt = bm self.Cb = dispData[2] self.Cf = farea[1] - self.Cm = cm \ No newline at end of file + self.Cm = cm 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..b343f9130 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 * 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 * From 55c63f414d506392b426a797e284feef1707ae7b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Thu, 21 Jan 2016 19:26:47 +0100 Subject: [PATCH 30/40] Corrected the translation environment --- src/Mod/Ship/shipGZ/TaskPanel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 0a4110e8b..1af0aa833 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -399,7 +399,7 @@ class TaskPanel: except ValueError: try: tooltip = str(QtGui.QApplication.translate( - "ship_areas", + "ship_gz", "GZ curve tool number of points selected", None, QtGui.QApplication.UnicodeUTF8)) @@ -415,7 +415,7 @@ class TaskPanel: except ValueError: try: tooltip = str(QtGui.QApplication.translate( - "ship_areas", + "ship_gz", "GZ curve tool variable trim angle selection", None, QtGui.QApplication.UnicodeUTF8)) From e1aa844e95d4f0203c1414dfd8f5138678c030e7 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Thu, 21 Jan 2016 19:57:55 +0100 Subject: [PATCH 31/40] Added the possibility of computing the transversal areas from the console interface --- src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/shipAreasCurve/TaskPanel.py | 23 ++-- src/Mod/Ship/shipHydrostatics/Tools.py | 131 ++++++++++++++++++----- 3 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index 1dbb91a1a..e0a3aefe5 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -29,4 +29,5 @@ __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 \ No newline at end of file +from shipCreateShip.Tools import createShip +from shipHydrostatics.Tools import areas \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.py b/src/Mod/Ship/shipAreasCurve/TaskPanel.py index 83b45da75..d3308ae7a 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.py +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.py @@ -51,28 +51,25 @@ class 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() + data = Hydrostatics.displacement(self.ship, - draft, + draft.getValueAs("m").Value, 0.0, - trim) + trim.getValueAs("deg").Value) disp = data[0] xcb = data[1].x data = Hydrostatics.areas(self.ship, - draft, - 0.0, - trim, - 0.0, - num) + 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 diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 628369ff2..8f005ad00 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -38,24 +38,102 @@ DENS = 1.025 # [tons/m3], salt water COMMON_BOOLEAN_ITERATIONS = 10 -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) +def getUnderwaterSide(shape): + """ Get the underwater shape, simply cropping the provided shape by the z=0 + free surface plane. + + Position arguments: + shape -- Solid shape to be cropped + + 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 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 + + if draft is None: + draft = ship.Draft + + # We will take a duplicate of ship shape in order to conveniently # manipulate it shape = ship.Shape.copy() - _draft = draft * Units.Metre.Value + # 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 @@ -63,26 +141,26 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): # 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)) + shape.translate(Vector(0.0, draft * math.sin(math.radians(roll)), -base_z)) # Trim and yaw 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.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) - shape.translate(Vector(0.0, 0.0, -_draft)) + shape.translate(Vector(draft * math.sin(math.radians(trim)), 0.0, 0.0)) + shape.translate(Vector(0.0, 0.0, -draft)) + + shape = getUnderwaterSide(shape) + # Sections distance computation bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax 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 @@ -96,12 +174,15 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30): try: f = Part.Face(shape.slice(Vector(1,0,0), x)) except Part.OCCError: - areas.append([x / Units.Metre.Value, 0.0]) + areas.append((Units.Quantity(x, Units.Length), + Units.Quantity(0.0, Units.Area))) continue # It is a valid face, so we can add this area - areas.append([x / Units.Metre.Value, f.Area / Units.Metre.Value**2]) + 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 From c65dbe1db1860a80b93b56ee248a53656b132e4d Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Thu, 21 Jan 2016 20:14:57 +0100 Subject: [PATCH 32/40] Implemented a generic method to move the ship shape in order to compute the hydrostatics --- src/Mod/Ship/shipHydrostatics/Tools.py | 57 +++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 8f005ad00..6d729e50f 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -38,8 +38,39 @@ DENS = 1.025 # [tons/m3], 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 value: + The same transformed input shape. Just for debugging purposes, you can + discard it. + """ + # 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 + + def getUnderwaterSide(shape): - """ Get the underwater shape, simply cropping the provided shape by the z=0 + """Get the underwater shape, simply cropping the provided shape by the z=0 free surface plane. Position arguments: @@ -130,25 +161,11 @@ def areas(ship, n, draft=None, if draft is None: draft = ship.Draft - # We will take a duplicate of ship shape in order to conveniently - # manipulate it - shape = ship.Shape.copy() - - # 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 and yaw 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)) - - shape = getUnderwaterSide(shape) + # Manipulate a copy of the ship shape + shape = getUnderwaterSide(placeShipShape(ship.Shape.copy(), + draft, + roll, + trim)) # Sections distance computation bbox = shape.BoundBox From 0a4a8191a27a519a1c189047664e5efc1665c49b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 22 Jan 2016 13:17:13 +0100 Subject: [PATCH 33/40] Added all the Hydrostatics to the console interface --- src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/shipAreasCurve/PlotAux.py | 4 +- src/Mod/Ship/shipAreasCurve/Preview.py | 6 +- src/Mod/Ship/shipAreasCurve/TaskPanel.py | 111 ++-- src/Mod/Ship/shipGZ/Tools.py | 8 +- src/Mod/Ship/shipHydrostatics/PlotAux.py | 57 +- src/Mod/Ship/shipHydrostatics/TaskPanel.py | 11 +- src/Mod/Ship/shipHydrostatics/Tools.py | 613 ++++++++++----------- 8 files changed, 371 insertions(+), 442 deletions(-) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index e0a3aefe5..bea30aae5 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -30,4 +30,5 @@ __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 \ No newline at end of file +from shipHydrostatics.Tools import areas, displacement, wettedArea, moment, + floatingArea, BMT, mainFrameCoeff \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/PlotAux.py b/src/Mod/Ship/shipAreasCurve/PlotAux.py index e83578e28..35c5df9a6 100644 --- a/src/Mod/Ship/shipAreasCurve/PlotAux.py +++ b/src/Mod/Ship/shipAreasCurve/PlotAux.py @@ -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 5a13718b8..44fbd6cf9 100644 --- a/src/Mod/Ship/shipAreasCurve/Preview.py +++ b/src/Mod/Ship/shipAreasCurve/Preview.py @@ -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 d3308ae7a..c32db44f5 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.py +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.py @@ -55,12 +55,11 @@ class TaskPanel: trim = Units.parseQuantity(Locale.fromString(form.trim.text())) num = form.num.value() - data = Hydrostatics.displacement(self.ship, - draft.getValueAs("m").Value, - 0.0, - trim.getValueAs("deg").Value) - disp = data[0] - xcb = data[1].x + disp, B, _ = Hydrostatics.displacement(self.ship, + draft, + Units.parseQuantity("0 deg"), + trim) + xcb = Units.Quantity(B.x, Units.Length) data = Hydrostatics.areas(self.ship, num, draft=draft, @@ -245,24 +244,11 @@ class TaskPanel: 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): @@ -279,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) @@ -319,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): @@ -363,10 +340,8 @@ class TaskPanel: 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 @@ -385,7 +360,7 @@ class TaskPanel: "AreaCurveDraft", "Ship", tooltip) - self.ship.AreaCurveDraft = '{} m'.format(draft) + self.ship.AreaCurveDraft = draft try: props.index("AreaCurveTrim") except ValueError: @@ -401,7 +376,7 @@ class TaskPanel: "AreaCurveTrim", "Ship", tooltip) - self.ship.AreaCurveTrim = '{} deg'.format(trim) + self.ship.AreaCurveTrim = trim try: props.index("AreaCurveNum") except ValueError: @@ -426,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/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index 6c91a54d4..a090b1ed3 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -121,8 +121,12 @@ def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): for i in range(MAX_EQUILIBRIUM_ITERS): # Get the displacement, and the bouyance application point - disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim) - disp *= 1000.0 * G + disp, B, _ = Hydrostatics.displacement(ship, + draft * Units.Metre, + roll * Units.Degree, + trim * Units.Degree) + disp = disp.getValueAs("kg").Value * G + B.multiply(1.0 / Units.Metre.Value) # Add the tanks effect on the center of gravity cog = Vector(COG.x * W, COG.y * W, COG.z * W) for i,t in enumerate(tanks): diff --git a/src/Mod/Ship/shipHydrostatics/PlotAux.py b/src/Mod/Ship/shipHydrostatics/PlotAux.py index 532d16f30..51702c1c4 100644 --- a/src/Mod/Ship/shipHydrostatics/PlotAux.py +++ b/src/Mod/Ship/shipHydrostatics/PlotAux.py @@ -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 f5c9d8107..c91f40912 100644 --- a/src/Mod/Ship/shipHydrostatics/TaskPanel.py +++ b/src/Mod/Ship/shipHydrostatics/TaskPanel.py @@ -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 6d729e50f..7c06842d0 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -34,7 +34,7 @@ from shipUtils import Math import shipUtils.Units as USys -DENS = 1.025 # [tons/m3], salt water +DENS = Units.parseQuantity("1025 kg/m^3") # Salt water COMMON_BOOLEAN_ITERATIONS = 10 @@ -49,9 +49,11 @@ def placeShipShape(shape, draft, roll, trim): roll -- Roll angle trim -- Trim angle - Returned value: - The same transformed input shape. Just for debugging purposes, you can - discard it. + 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: @@ -66,16 +68,21 @@ def placeShipShape(shape, draft, roll, trim): shape.translate(Vector(draft * math.sin(math.radians(trim)), 0.0, 0.0)) shape.translate(Vector(0.0, 0.0, -draft)) - return shape + return shape, base_z -def getUnderwaterSide(shape): +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 """ @@ -109,7 +116,7 @@ def getUnderwaterSide(shape): "UnderwaterSideHelper") common.Shapes = [orig, box] App.ActiveDocument.recompute() - if len(common.Shape.Solids) == 0: + 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( @@ -139,7 +146,7 @@ def getUnderwaterSide(shape): def areas(ship, n, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): - """ Compute the ship transversal areas + """Compute the ship transversal areas Position arguments: ship -- Ship object (see createShip) @@ -161,11 +168,8 @@ def areas(ship, n, draft=None, if draft is None: draft = ship.Draft - # Manipulate a copy of the ship shape - shape = getUnderwaterSide(placeShipShape(ship.Shape.copy(), - draft, - roll, - trim)) + shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) # Sections distance computation bbox = shape.BoundBox @@ -191,6 +195,12 @@ def areas(ship, n, draft=None, try: f = Part.Face(shape.slice(Vector(1,0,0), x)) except Part.OCCError: + 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 @@ -204,397 +214,328 @@ def areas(ship, n, draft=None, 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() - _draft = draft * Units.Metre.Value - # 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 and yaw 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.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) - shape.translate(Vector(0.0, 0.0, -_draft)) - Part.show(shape) - ship_shape = App.ActiveDocument.Objects[-1] + if draft is None: + draft = ship.Draft - 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", - "DisplacementHelper") - common.Shapes = [ship_shape, 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", - "Boolean operation failed. The tool is retrying that 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() + shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() - if len(common.Shape.Solids) > 0: - for solid in common.Shape.Solids: - vol += solid.Volume / Units.Metre.Value**3 + if len(shape.Solids) > 0: + for solid in shape.Solids: + vol += solid.Volume sCoG = solid.CenterOfMass - cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4 - cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4 - cog.z = cog.z + sCoG.z * solid.Volume / Units.Metre.Value**4 + 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 - Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3 - App.ActiveDocument.removeObject(common.Name) - App.ActiveDocument.removeObject(ship_shape.Name) - App.ActiveDocument.removeObject(box.Name) - App.ActiveDocument.recompute() + bbox = shape.BoundBox + Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) - # Undo the transformations + # 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.rotateZ(-math.radians(yaw)) m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0)) m.rotateY(math.radians(trim)) m.move(Vector(0.0, -draft * math.sin(math.radians(roll)), - base_z / Units.Metre.Value)) + base_z)) m.rotateX(-math.radians(roll)) B.transform(m) - # Return the computed data - return [DENS*vol, Vector(B.X, B.Y, B.Z), 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() - _draft = draft * Units.Metre.Value - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.translate(Vector(0.0, 0.0, -_draft)) - - bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax - - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - 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() - _draft = draft * Units.Metre.Value - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.translate(Vector(0.0, 0.0, -_draft)) + 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 - ymin = bbox.YMin - ymax = bbox.YMax - - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - 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 - ymin = bbox.YMin - ymax = bbox.YMax + Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin) - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - 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 From a615380027ced6e1fdd0c3a6daee397682c5bf91 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Sat, 23 Jan 2016 14:21:25 +0100 Subject: [PATCH 34/40] Added a console interface to the weights generation --- src/Mod/Ship/CMakeLists.txt | 1 + src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/WeightInstance.py | 49 +++++++----- src/Mod/Ship/shipCreateWeight/TaskPanel.py | 33 +------- src/Mod/Ship/shipCreateWeight/Tools.py | 91 ++++++++++++++++++++++ 5 files changed, 126 insertions(+), 51 deletions(-) create mode 100644 src/Mod/Ship/shipCreateWeight/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index c44c5e7e9..50e42a3c2 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -63,6 +63,7 @@ SET(ShipCreateWeight_SRCS shipCreateWeight/__init__.py shipCreateWeight/TaskPanel.py shipCreateWeight/TaskPanel.ui + shipCreateWeight/Tools.py ) SOURCE_GROUP("shipcreateweight" FILES ${ShipCreateWeight_SRCS}) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index bea30aae5..1509f38e2 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -31,4 +31,5 @@ __doc__="The Ships module provide a set of tools to make some specific Naval" \ from shipCreateShip.Tools import createShip from shipHydrostatics.Tools import areas, displacement, wettedArea, moment, - floatingArea, BMT, mainFrameCoeff \ No newline at end of file + floatingArea, BMT, mainFrameCoeff +from shipCreateWeight.Tools import createWeight \ No newline at end of file diff --git a/src/Mod/Ship/WeightInstance.py b/src/Mod/Ship/WeightInstance.py index c524c85db..8a78c5d61 100644 --- a/src/Mod/Ship/WeightInstance.py +++ b/src/Mod/Ship/WeightInstance.py @@ -36,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. @@ -75,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", @@ -100,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. """ @@ -110,7 +110,7 @@ class Weight: def execute(self, fp): """Detects the entity recomputations. - Keyword arguments: + Position arguments: fp -- Part::FeaturePython object affected. """ pass @@ -118,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. """ @@ -127,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. """ @@ -138,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. """ @@ -149,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. """ @@ -161,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. """ @@ -191,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. """ @@ -205,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. """ @@ -219,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. """ @@ -234,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'), @@ -262,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: diff --git a/src/Mod/Ship/shipCreateWeight/TaskPanel.py b/src/Mod/Ship/shipCreateWeight/TaskPanel.py index cd8a312a5..390ce04b6 100644 --- a/src/Mod/Ship/shipCreateWeight/TaskPanel.py +++ b/src/Mod/Ship/shipCreateWeight/TaskPanel.py @@ -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,38 +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 - ship.Proxy.cleanWeights(ship) - ship.Proxy.cleanTanks(ship) - ship.Proxy.cleanLoadConditions(ship) - - 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 From c3ccd0f72ce9d25bd7fd6d0f96e643dcdce50b33 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Sat, 23 Jan 2016 14:35:56 +0100 Subject: [PATCH 35/40] Added a console interface to the tanks generation --- src/Mod/Ship/CMakeLists.txt | 1 + src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/shipCreateTank/TaskPanel.py | 15 +------ src/Mod/Ship/shipCreateTank/Tools.py | 56 ++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 src/Mod/Ship/shipCreateTank/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index 50e42a3c2..b39f276c3 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -71,6 +71,7 @@ SET(ShipCreateTank_SRCS shipCreateTank/__init__.py shipCreateTank/TaskPanel.py shipCreateTank/TaskPanel.ui + shipCreateTank/Tools.py ) SOURCE_GROUP("shipcreatetank" FILES ${ShipCreateTank_SRCS}) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index 1509f38e2..15510e994 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -32,4 +32,5 @@ __doc__="The Ships module provide a set of tools to make some specific Naval" \ from shipCreateShip.Tools import createShip from shipHydrostatics.Tools import areas, displacement, wettedArea, moment, floatingArea, BMT, mainFrameCoeff -from shipCreateWeight.Tools import createWeight \ No newline at end of file +from shipCreateWeight.Tools import createWeight +from shipCreateTank.Tools import createTank \ No newline at end of file diff --git a/src/Mod/Ship/shipCreateTank/TaskPanel.py b/src/Mod/Ship/shipCreateTank/TaskPanel.py index 01a141709..08fb43fcb 100644 --- a/src/Mod/Ship/shipCreateTank/TaskPanel.py +++ b/src/Mod/Ship/shipCreateTank/TaskPanel.py @@ -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,21 +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 - ship.Proxy.cleanWeights(ship) - ship.Proxy.cleanTanks(ship) - ship.Proxy.cleanLoadConditions(ship) - - 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 From 83d52177957106d2452c265aefd9e59bffd3b519 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 25 Jan 2016 08:50:57 +0100 Subject: [PATCH 36/40] Added the capacity curve computation tool to the console interface --- src/Mod/Ship/CMakeLists.txt | 3 +- src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/shipCapacityCurve/TaskPanel.py | 48 ++++++----------- src/Mod/Ship/shipCapacityCurve/Tools.py | 60 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 src/Mod/Ship/shipCapacityCurve/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index b39f276c3..f1c2c74be 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -80,11 +80,12 @@ 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/__init__.py ) SOURCE_GROUP("shipcreateloadcondition" FILES ${ShipCreateLoadCondition_SRCS}) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index 15510e994..df1af2558 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -33,4 +33,5 @@ from shipCreateShip.Tools import createShip from shipHydrostatics.Tools import areas, displacement, wettedArea, moment, floatingArea, BMT, mainFrameCoeff from shipCreateWeight.Tools import createWeight -from shipCreateTank.Tools import createTank \ No newline at end of file +from shipCreateTank.Tools import createTank +from shipCapacityCurve.Tools import tankCapacityCurve \ No newline at end of file diff --git a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py index 1118f22e7..f7cfd9ee9 100644 --- a/src/Mod/Ship/shipCapacityCurve/TaskPanel.py +++ b/src/Mod/Ship/shipCapacityCurve/TaskPanel.py @@ -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,36 +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 = 1.0 / (n - 1) - l = [0.0] - v = [0.0] - z = [0.0] - - 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 = self.tank.Proxy.getVolume(self.tank, level) - l.append(level * 100.0) - z.append(level * 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 From f0770a7456eeaebe83d285494d9a0205ef9fb098 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 25 Jan 2016 09:11:19 +0100 Subject: [PATCH 37/40] Added the loading conditions creation to the console interface --- src/Mod/Ship/CMakeLists.txt | 1 + src/Mod/Ship/Ship.py | 7 +- src/Mod/Ship/shipCreateLoadCondition/Tools.py | 124 ++++++++++++++++++ .../Ship/shipCreateLoadCondition/__init__.py | 121 +---------------- 4 files changed, 132 insertions(+), 121 deletions(-) create mode 100644 src/Mod/Ship/shipCreateLoadCondition/Tools.py diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index f1c2c74be..ca52205e7 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -86,6 +86,7 @@ SOURCE_GROUP("shipcapacitycurve" FILES ${ShipCapacityCurve_SRCS}) SET(ShipCreateLoadCondition_SRCS shipCreateLoadCondition/__init__.py + shipCreateLoadCondition/Tools.py ) SOURCE_GROUP("shipcreateloadcondition" FILES ${ShipCreateLoadCondition_SRCS}) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index df1af2558..840711b52 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -30,8 +30,9 @@ __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, - floatingArea, BMT, mainFrameCoeff +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 \ No newline at end of file +from shipCapacityCurve.Tools import tankCapacityCurve +from shipCreateLoadCondition.Tools import createLoadCondition \ No newline at end of file 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 index b33214bd5..7515040c5 100644 --- a/src/Mod/Ship/shipCreateLoadCondition/__init__.py +++ b/src/Mod/Ship/shipCreateLoadCondition/__init__.py @@ -23,7 +23,8 @@ import FreeCAD as App import FreeCADGui as Gui -import Spreadsheet +from PySide import QtGui +import Tools READ_ONLY_FOREGROUND = (0.5, 0.5, 0.5) @@ -73,120 +74,4 @@ def load(): App.Console.PrintError(msg + '\n') return - # Create the spreadsheet - s = App.activeDocument().addObject('Spreadsheet::Sheet', - 'LoadCondition') - - # Add a description - s.setForeground('A1:B2', READ_ONLY_FOREGROUND) - s.setBackground('A1:B2', READ_ONLY_BACKGROUND) - s.setAlignment('B1:B2', 'center', 'keep') - s.setStyle('B1:B2', 'italic', 'add') - s.set("A1", "Ship:") - s.set("A2", "Load condition:") - s.set("B1", "=" + ship.Name + ".Label") - s.set("B2", "=Label") - - # Add the weights data - s.setAlignment('A4:A5', 'center', 'keep') - s.setStyle('A4:A5', 'bold', 'add') - s.setStyle('A4:A5', 'underline', 'add') - s.set("A4", "WEIGHTS DATA") - s.set("A5", "name") - for i in range(len(ship.Weights)): - weight = App.activeDocument().getObject(ship.Weights[i]) - s.set("A{}".format(i + 6), "=" + weight.Name + ".Label") - s.setForeground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_FOREGROUND) - s.setBackground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_BACKGROUND) - - # Add the tanks data - s.mergeCells('C4:E4') - s.setForeground('C4:E5', READ_ONLY_FOREGROUND) - s.setBackground('C4:E5', READ_ONLY_BACKGROUND) - s.setAlignment('C4:E5', 'center', 'keep') - s.setStyle('C4:E5', 'bold', 'add') - s.setStyle('C4:E5', 'underline', 'add') - s.set("C4", "TANKS DATA") - s.set("C5", "name") - s.set("D5", "Fluid density [kg/m^3]") - s.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]) - s.set("C{}".format(i + 6), "=" + tank.Name + ".Label") - s.set("D{}".format(i + 6), "998.0") - s.set("E{}".format(i + 6), "0.0") - s.setForeground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_FOREGROUND) - s.setBackground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_BACKGROUND) - - s.setColumnWidth('A', 128) - s.setColumnWidth('B', 128) - s.setColumnWidth('C', 128) - s.setColumnWidth('D', 150) - s.setColumnWidth('E', 200) - - """ - # Add a reference to the owner ship - s.mergeCells('A1:D1') - s.setAlignment('A1:B2', 'center', 'keep') - s.setStyle('A1:B2', 'bold', 'add') - s.setStyle('A1:B2', 'underline', 'add') - s.set("A1", "SHIP DATA") - s.set("A2", "ship") - s.set("A3", ship.Label) - s.set("B2", "internal ref") - s.set("B3", ship.Name) - s.setForeground('A1:B3', (0.5,0.5,0.5)) - - # Clean the Ship instance before generating the load condition - ship.Proxy.cleanWeights(ship) - ship.Proxy.cleanTanks(ship) - - # Add the weights data - s.mergeCells('A4:D4') - s.setAlignment('A4:B5', 'center', 'keep') - s.setStyle('A4:B5', 'bold', 'add') - s.setStyle('A4:B5', 'underline', 'add') - s.set("A4", "WEIGHTS DATA") - s.set("A5", "weight") - s.set("B5", "internal ref") - for i in range(len(ship.Weights)): - weight = App.activeDocument().getObject(ship.Weights[i]) - s.set("A{}".format(i + 6), weight.Label) - s.set("B{}".format(i + 6), weight.Name) - s.setForeground('A4:B{}'.format(5 + len(ship.Weights)), (0.5,0.5,0.5)) - - # Add the tanks data - s.mergeCells('A{0}:D{0}'.format(6 + len(ship.Weights))) - s.setAlignment('A{0}:A{0}'.format(6 + len(ship.Weights)), 'center', 'keep') - s.setAlignment('A{0}:D{0}'.format(7 + len(ship.Weights)), 'center', 'keep') - s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'bold', 'add') - s.setStyle('A{0}:D{0}'.format(7 + len(ship.Weights)), 'bold', 'add') - s.setStyle('A{0}:A{0}'.format(6 + len(ship.Weights)), 'underline', 'add') - s.setStyle('A{0}:D{0}'.format(7 + len(ship.Weights)), 'underline', 'add') - s.set("A{}".format(6 + len(ship.Weights)), "TANKS DATA") - s.set("A{}".format(7 + len(ship.Weights)), "tank") - s.set("B{}".format(7 + len(ship.Weights)), "internal ref") - s.set("C{}".format(7 + len(ship.Weights)), "Fluid density [kg/m^3]") - s.set("D{}".format(7 + len(ship.Weights)), "Filling ratio (interval [0.0,1.0])") - for i in range(len(ship.Tanks)): - tank = App.activeDocument().getObject(ship.Tanks[i]) - s.set("A{}".format(i + 8 + len(ship.Weights)), tank.Label) - s.set("B{}".format(i + 8 + len(ship.Weights)), tank.Name) - s.set("C{}".format(i + 8 + len(ship.Weights)), "998.0") - s.set("D{}".format(i + 8 + len(ship.Weights)), "0.0") - s.setForeground('A{0}:A{0}'.format(6 + len(ship.Weights)), (0.5,0.5,0.5)) - s.setForeground('A{0}:D{0}'.format(7 + len(ship.Weights)), (0.5,0.5,0.5)) - s.setForeground('A{}:B{}'.format(8 + len(ship.Weights), - 8 + len(ship.Weights) + len(ship.Tanks)), - (0.5,0.5,0.5)) - """ - - # Add the spreadsheet to the list of loading conditions of the ship - lcs = ship.LoadConditions[:] - lcs.append(s.Name) - ship.LoadConditions = lcs - ship.Proxy.cleanLoadConditions(ship) - - # Recompute to take the changes - App.activeDocument().recompute() \ No newline at end of file + Tools.createLoadCondition(ship) \ No newline at end of file From 499c685668bb72d7e84472c3f42c5814c3502828 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 25 Jan 2016 15:15:04 +0100 Subject: [PATCH 38/40] Added the GZ curves computation tool to the console interface --- src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/TankInstance.py | 24 +-- src/Mod/Ship/shipGZ/TaskPanel.py | 115 ++--------- src/Mod/Ship/shipGZ/Tools.py | 276 +++++++++++++++++++------ src/Mod/Ship/shipHydrostatics/Tools.py | 8 +- 5 files changed, 248 insertions(+), 178 deletions(-) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index 840711b52..426d58af7 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -35,4 +35,5 @@ from shipHydrostatics.Tools import floatingArea, BMT, mainFrameCoeff from shipCreateWeight.Tools import createWeight from shipCreateTank.Tools import createTank from shipCapacityCurve.Tools import tankCapacityCurve -from shipCreateLoadCondition.Tools import createLoadCondition \ No newline at end of file +from shipCreateLoadCondition.Tools import createLoadCondition +from shipGZ.Tools import gz \ No newline at end of file diff --git a/src/Mod/Ship/TankInstance.py b/src/Mod/Ship/TankInstance.py index c6bb90ccf..053259b35 100644 --- a/src/Mod/Ship/TankInstance.py +++ b/src/Mod/Ship/TankInstance.py @@ -154,7 +154,8 @@ class Tank: return ret_value - def getCoG(self, fp, vol, roll=0.0, trim=0.0): + def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): """Return the fluid volume center of gravity, provided the volume of fluid inside the tank. @@ -162,15 +163,14 @@ class Tank: Keyword arguments: fp -- Part::FeaturePython object affected. - vol -- Volume of fluid (in m^3). - roll -- Ship roll angle (in degrees). - trim -- Ship trim angle (in degrees). + vol -- Volume of fluid. + roll -- Ship roll angle. + trim -- Ship trim angle. If the fluid volume is bigger than the total tank one, it will be conveniently clamped. """ # Change the units of the volume, and clamp the value - vol = vol * Units.Metre.Value**3 if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: @@ -187,19 +187,19 @@ class Tank: return cog # Get a first estimation of the level - level = vol / fp.Shape.Volume + level = vol.Value / fp.Shape.Volume # Transform the tank shape current_placement = fp.Placement m = current_placement.toMatrix() - m.rotateX(radians(roll)) - m.rotateY(-radians(trim)) - fp.Placement = m + m.rotateX(roll.getValueAs("rad")) + m.rotateY(-trim.getValueAs("rad")) + fp.Placement = Placement(m) # Iterate to find the fluid shape for i in range(COMMON_BOOLEAN_ITERATIONS): shape = self.getVolume(fp, level, return_shape=True) - error = (vol - shape.Volume) / fp.Shape.Volume + error = (vol.Value - shape.Volume) / fp.Shape.Volume if abs(error) < 0.01: break level += error @@ -222,8 +222,8 @@ class Tank: fp.Placement = current_placement p = Part.Point(cog) m = Matrix() - m.rotateY(radians(trim)) - m.rotateX(-radians(roll)) + m.rotateY(trim.getValueAs("rad")) + m.rotateX(-roll.getValueAs("rad")) p.rotate(Placement(m)) return Vector(p.X, p.Y, p.Z) diff --git a/src/Mod/Ship/shipGZ/TaskPanel.py b/src/Mod/Ship/shipGZ/TaskPanel.py index 1af0aa833..c5dc76255 100644 --- a/src/Mod/Ship/shipGZ/TaskPanel.py +++ b/src/Mod/Ship/shipGZ/TaskPanel.py @@ -48,18 +48,22 @@ class TaskPanel: form.n_points = self.widget(QtGui.QSpinBox, "NumPoints") form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim") - rolls = [] - roll = Units.Quantity(Locale.fromString( - form.angle.text())).getValueAs('deg').Value + roll = Units.Quantity(Locale.fromString(form.angle.text())) n_points = form.n_points.value() + var_trim = form.var_trim.isChecked() + + rolls = [] for i in range(n_points): rolls.append(roll * i / float(n_points - 1)) - gzs, drafts, trims = Tools.solve(self.ship, - self.weights, - self.tanks, - rolls, - form.var_trim.isChecked()) + points = Tools.gz(self.lc, rolls, var_trim) + gzs = [] + drafts = [] + trims = [] + for p in points: + gzs.append(p[0].getValueAs('m').Value) + drafts.append(p[1].getValueAs('m').Value) + trims.append(p[2].getValueAs('deg').Value) PlotAux.Plot(rolls, gzs, drafts, trims) @@ -175,99 +179,6 @@ class TaskPanel: continue except ValueError: continue - # Extract the weights and the tanks - weights = [] - index = 6 - while True: - try: - ws = doc.getObjectsByLabel(obj.get('A{}'.format(index))) - except ValueError: - break - index += 1 - if len(ws) != 1: - if len(ws) == 0: - msg = QtGui.QApplication.translate( - "ship_console", - "Wrong Weight label! (no instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - obj.get('A{}'.format(index - 1)))) - else: - msg = QtGui.QApplication.translate( - "ship_console", - "Ambiguous Weight label! ({} instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('A{}'.format(index - 1)))) - continue - w = ws[0] - try: - if w is None or not w.PropertiesList.index("IsWeight"): - msg = QtGui.QApplication.translate( - "ship_console", - "Invalid Weight! (the object labeled as" - "'{}' is not a weight)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('A{}'.format(index - 1)))) - continue - except ValueError: - continue - weights.append(w) - tanks = [] - index = 6 - while True: - try: - ts = doc.getObjectsByLabel(obj.get('C{}'.format(index))) - dens = float(obj.get('D{}'.format(index))) - level = float(obj.get('E{}'.format(index))) - except ValueError: - break - index += 1 - if len(ts) != 1: - if len(ts) == 0: - msg = QtGui.QApplication.translate( - "ship_console", - "Wrong Tank label! (no instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - obj.get('C{}'.format(index - 1)))) - else: - msg = QtGui.QApplication.translate( - "ship_console", - "Ambiguous Tank label! ({} instances labeled as" - "'{}' found)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ts), - obj.get('C{}'.format(index - 1)))) - continue - t = ts[0] - try: - if t is None or not t.PropertiesList.index("IsTank"): - msg = QtGui.QApplication.translate( - "ship_console", - "Invalid Tank! (the object labeled as" - "'{}' is not a tank)", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + '\n'.format( - len(ws), - obj.get('C{}'.format(index - 1)))) - continue - except ValueError: - continue - tanks.append((t, dens, level)) # Let's see if several loading conditions have been selected (and # prompt a warning) if self.lc: @@ -281,8 +192,6 @@ class TaskPanel: break self.lc = obj self.ship = ship - self.weights = weights - self.tanks = tanks if not self.lc: msg = QtGui.QApplication.translate( "ship_console", diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index a090b1ed3..c3bad14b2 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -22,72 +22,79 @@ #*************************************************************************** import math +import FreeCAD as App +import FreeCADGui as Gui from FreeCAD import Vector, Matrix, Placement import Part import Units -import FreeCAD as App -import FreeCADGui as Gui import Instance as ShipInstance import WeightInstance import TankInstance from shipHydrostatics import Tools as Hydrostatics -G = 9.81 +G = Units.parseQuantity("9.81 m/s^2") MAX_EQUILIBRIUM_ITERS = 10 -DENS = 1.025 # [tons/m3], salt water +DENS = Units.parseQuantity("1025 kg/m^3") TRIM_RELAX_FACTOR = 10.0 def solve(ship, weights, tanks, rolls, var_trim=True): - """ Compute the ship GZ curve. - @param ship Ship instance. - @param weights Considered weights. - @param tanks Considered tanks. - @param rolls List of considered roll angles. - @param var_trim True if the trim angle should be recomputed at each roll - angle, False otherwise. - @return GZ values, drafts and trim angles, for each roll angle (in 3 - separated lists) + """Compute the ship GZ stability curve + + Position arguments: + ship -- Ship object + weights -- List of weights to consider + tanks -- List of tanks to consider (each one should be a tuple with the + tank instance, the density of the fluid inside, and the filling level ratio) + rolls -- List of roll angles + + Keyword arguments: + var_trim -- True if the equilibrium trim should be computed for each roll + angle, False if null trim angle can be used instead. + + Returned value: + List of GZ curve points. Each point contains the GZ stability length, the + equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is + False) """ # Get the unloaded weight (ignoring the tanks for the moment). - W = 0.0 - COG = Vector() + W = Units.parseQuantity("0 kg") + mom_x = Units.parseQuantity("0 kg*m") + mom_y = Units.parseQuantity("0 kg*m") + mom_z = Units.parseQuantity("0 kg*m") for w in weights: - W += w.Proxy.getMass(w).getValueAs('kg').Value + W += w.Proxy.getMass(w) m = w.Proxy.getMoment(w) - COG.x += m[0].getValueAs('kg*m').Value - COG.y += m[1].getValueAs('kg*m').Value - COG.z += m[2].getValueAs('kg*m').Value - COG = COG.multiply(1.0 / W) + mom_x += m[0] + mom_y += m[1] + mom_z += m[2] + COG = Vector(mom_x / W, mom_y / W, mom_z / W) W = W * G # Get the tanks weight - TW = 0.0 + TW = Units.parseQuantity("0 kg") VOLS = [] for t in tanks: # t[0] = tank object # t[1] = load density # t[2] = filling level - vol = t[0].Proxy.getVolume(t[0], t[2]).getValueAs('m^3').Value + vol = t[0].Proxy.getVolume(t[0], t[2]) VOLS.append(vol) TW += vol * t[1] TW = TW * G - gzs = [] - drafts = [] - trims = [] + points = [] for i,roll in enumerate(rolls): App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls))) - gz, draft, trim = solve_point(W, COG, TW, VOLS, - ship, tanks, roll, var_trim) - if gz is None: + point = solve_point(W, COG, TW, VOLS, + ship, tanks, roll, var_trim) + if point is None: return [] - gzs.append(gz) - drafts.append(draft) - trims.append(trim) + points.append(point) + + return points - return gzs, drafts, trims def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): """ Compute the ship GZ value. @@ -101,56 +108,209 @@ def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): angle, False otherwise. @return GZ value, equilibrium draft, and equilibrium trim angle (0 if variable trim has not been requested) - """ - gz = 0.0 - + """ # Look for the equilibrium draft (and eventually the trim angle too) - max_draft = ship.Shape.BoundBox.ZMax / Units.Metre.Value - draft = ship.Draft.getValueAs('m').Value - max_disp = ship.Shape.Volume / Units.Metre.Value**3 * DENS * 1000.0 * G + max_draft = Units.Quantity(ship.Shape.BoundBox.ZMax, Units.Length) + draft = ship.Draft + max_disp = Units.Quantity(ship.Shape.Volume, Units.Volume) * DENS * G if max_disp < W + TW: msg = QtGui.QApplication.translate( "ship_console", "Too much weight! The ship will never displace water enough", None, QtGui.QApplication.UnicodeUTF8) - App.Console.PrintError(msg + ' ({} tons vs. {} tons)\n'.format( - max_disp / 1000.0 / G, (W + TW) / 1000.0 / G)) + App.Console.PrintError(msg + ' ({} vs. {})\n'.format( + (max_disp / G).UserString, ((W + TW) / G).UserString)) return None - trim = 0.0 + trim = Units.parseQuantity("0 deg") for i in range(MAX_EQUILIBRIUM_ITERS): # Get the displacement, and the bouyance application point disp, B, _ = Hydrostatics.displacement(ship, - draft * Units.Metre, - roll * Units.Degree, - trim * Units.Degree) - disp = disp.getValueAs("kg").Value * G - B.multiply(1.0 / Units.Metre.Value) + draft, + roll, + trim) + disp *= G + # Add the tanks effect on the center of gravity - cog = Vector(COG.x * W, COG.y * W, COG.z * W) + mom_x = Units.Quantity(COG.x, Units.Length) * W + mom_y = Units.Quantity(COG.y, Units.Length) * W + mom_z = Units.Quantity(COG.z, Units.Length) * W for i,t in enumerate(tanks): tank_weight = VOLS[i] * t[1] * G - cog += t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim).multiply( - tank_weight / Units.Metre.Value) - cog = cog.multiply(1.0 / (W + TW)) + tank_cog = t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim) + mom_x += Units.Quantity(tank_cog.x, Units.Length) * tank_weight + mom_y += Units.Quantity(tank_cog.y, Units.Length) * tank_weight + mom_z += Units.Quantity(tank_cog.z, Units.Length) * tank_weight + cog_x = mom_x / (W + TW) + cog_y = mom_y / (W + TW) + cog_z = mom_z / (W + TW) # Compute the errors - draft_error = -(disp - W - TW) / max_disp - R = cog - B + draft_error = -((disp - W - TW) / max_disp).Value + R_x = cog_x - Units.Quantity(B.x, Units.Length) + R_y = cog_y - Units.Quantity(B.y, Units.Length) + R_z = cog_z - Units.Quantity(B.z, Units.Length) if not var_trim: trim_error = 0.0 else: - trim_error = -TRIM_RELAX_FACTOR * R.x / ship.Length.getValueAs('m').Value + trim_error = -TRIM_RELAX_FACTOR * R_x / ship.Length # Check if we can tolerate the errors - if abs(draft_error) < 0.01 and abs(trim_error) < 0.05: + if abs(draft_error) < 0.01 and abs(trim_error) < 0.1: break # Get the new draft and trim draft += draft_error * max_draft - trim += trim_error + trim += trim_error * Units.Degree # GZ should be provided in the Free surface oriented frame of reference - c = math.cos(math.radians(roll)) - s = math.sin(math.radians(roll)) - return c * R.y - s * R.z, draft, trim \ No newline at end of file + c = math.cos(roll.getValueAs('rad')) + s = math.sin(roll.getValueAs('rad')) + return c * R_y - s * R_z, draft, trim + + +def gz(lc, rolls, var_trim=True): + """Compute the ship GZ stability curve + + Position arguments: + lc -- Load condition spreadsheet + rolls -- List of roll angles to compute + + Keyword arguments: + var_trim -- True if the equilibrium trim should be computed for each roll + angle, False if null trim angle can be used instead. + + Returned value: + List of GZ curve points. Each point contains the GZ stability length, the + equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is + False) + """ + # B1 cell must be a ship + # B2 cell must be the loading condition itself + doc = lc.Document + try: + if lc not in doc.getObjectsByLabel(lc.get('B2')): + return[] + ships = doc.getObjectsByLabel(lc.get('B1')) + if len(ships) != 1: + if len(ships) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Ship label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('B1'))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Ship label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ships), + lc.get('B1'))) + return[] + ship = ships[0] + if ship is None or not ship.PropertiesList.index("IsShip"): + return[] + except ValueError: + return[] + # Extract the weights and the tanks + weights = [] + index = 6 + while True: + try: + ws = doc.getObjectsByLabel(lc.get('A{}'.format(index))) + except ValueError: + break + index += 1 + if len(ws) != 1: + if len(ws) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Weight label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('A{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Weight label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('A{}'.format(index - 1)))) + continue + w = ws[0] + try: + if w is None or not w.PropertiesList.index("IsWeight"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Weight! (the object labeled as" + "'{}' is not a weight)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('A{}'.format(index - 1)))) + continue + except ValueError: + continue + weights.append(w) + tanks = [] + index = 6 + while True: + try: + ts = doc.getObjectsByLabel(lc.get('C{}'.format(index))) + dens = float(lc.get('D{}'.format(index))) + level = float(lc.get('E{}'.format(index))) + dens = Units.parseQuantity("{} kg/m^3".format(dens)) + except ValueError: + break + index += 1 + if len(ts) != 1: + if len(ts) == 0: + msg = QtGui.QApplication.translate( + "ship_console", + "Wrong Tank label! (no instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + lc.get('C{}'.format(index - 1)))) + else: + msg = QtGui.QApplication.translate( + "ship_console", + "Ambiguous Tank label! ({} instances labeled as" + "'{}' found)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ts), + lc.get('C{}'.format(index - 1)))) + continue + t = ts[0] + try: + if t is None or not t.PropertiesList.index("IsTank"): + msg = QtGui.QApplication.translate( + "ship_console", + "Invalid Tank! (the object labeled as" + "'{}' is not a tank)", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n'.format( + len(ws), + lc.get('C{}'.format(index - 1)))) + continue + except ValueError: + continue + tanks.append((t, dens, level)) + + return solve(ship, weights, tanks, rolls, var_trim) \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 7c06842d0..5460d9dd5 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -261,12 +261,12 @@ def displacement(ship, draft=None, B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) - m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0)) - m.rotateY(math.radians(trim)) + m.move(Vector(-draft * math.sin(trim.getValueAs("rad")), 0.0, 0.0)) + m.rotateY(trim.getValueAs("rad")) m.move(Vector(0.0, - -draft * math.sin(math.radians(roll)), + -draft * math.sin(roll.getValueAs("rad")), base_z)) - m.rotateX(-math.radians(roll)) + m.rotateX(-roll.getValueAs("rad")) B.transform(m) try: From 621f7f49e0bbf2b6890d89f916e3f5723567bbc1 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 25 Jan 2016 15:29:53 +0100 Subject: [PATCH 39/40] Fixed index typo --- src/Mod/Ship/shipLoadExample/TaskPanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Ship/shipLoadExample/TaskPanel.py b/src/Mod/Ship/shipLoadExample/TaskPanel.py index b343f9130..d9542c37a 100644 --- a/src/Mod/Ship/shipLoadExample/TaskPanel.py +++ b/src/Mod/Ship/shipLoadExample/TaskPanel.py @@ -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 From d6ca807b71d49f1b7c7f7149eb799f028f07c043 Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Mon, 25 Jan 2016 15:38:23 +0100 Subject: [PATCH 40/40] Updated the windows installer --- src/WindowsInstaller/ModShip.wxi | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) 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 @@ +