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()