diff --git a/src/Mod/Ship/CMakeLists.txt b/src/Mod/Ship/CMakeLists.txt index d2cd46991..6542ddda2 100644 --- a/src/Mod/Ship/CMakeLists.txt +++ b/src/Mod/Ship/CMakeLists.txt @@ -77,6 +77,7 @@ SOURCE_GROUP("shipareascurve" FILES ${ShipAreasCurve_SRCS}) SET(ShipHydrostatics_SRCS shipHydrostatics/__init__.py + shipHydrostatics/Plot.py shipHydrostatics/TaskPanel.py shipHydrostatics/TaskPanel.ui shipHydrostatics/Tools.py diff --git a/src/Mod/Ship/Makefile.am b/src/Mod/Ship/Makefile.am index d572d41d6..380d8aed8 100644 --- a/src/Mod/Ship/Makefile.am +++ b/src/Mod/Ship/Makefile.am @@ -54,6 +54,7 @@ nobase_data_DATA = \ shipAreasCurve/TaskPanel.py \ shipAreasCurve/TaskPanel.ui \ shipHydrostatics/__init__.py \ + shipHydrostatics/Plot.py \ shipHydrostatics/TaskPanel.py \ shipHydrostatics/TaskPanel.ui \ shipHydrostatics/Tools.py \ diff --git a/src/Mod/Ship/shipHydrostatics/Plot.py b/src/Mod/Ship/shipHydrostatics/Plot.py new file mode 100644 index 000000000..72e81a0a8 --- /dev/null +++ b/src/Mod/Ship/shipHydrostatics/Plot.py @@ -0,0 +1,194 @@ +#*************************************************************************** +#* * +#* 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 +# FreeCAD modules +import FreeCAD,FreeCADGui +from FreeCAD import Part, Base +from FreeCAD import Image, ImageGui +# FreeCADShip modules +from shipUtils import Paths, Translator +import Tools + +header = """ ################################################################# + + ##### #### ### #### ##### # # ### #### + # # # # # # # # # # # # + # ## #### #### # # # # # # # # # # # + #### # # # # # # # ##### # # ## ## ##### # #### + # # #### #### # # # # # # # # # # + # # # # # # # # # # # # # # + # # #### #### ### # # #### ##### # # ### # + + ################################################################# +""" + +class Plot(object): + def __init__(self, ship, trim, drafts): + """ Constructor. performs plot and show it (Using pyxplot). + @param ship Selected ship instance + @param trim Trim in degrees. + @param drafts List of drafts to be performed. + """ + if self.createDirectory(): + return + if self.saveData(ship, trim, drafts): + return + if self.saveLayout(trim): + return + if self.execute(): + return + ImageGui.open(self.path + 'volume.png') + + def createDirectory(self): + """ Create needed folder to write data and scripts. + @return True if error happens. + """ + self.path = FreeCAD.ConfigGet("UserAppData") + "ShipOutput/" + if not os.path.exists(self.path): + os.makedirs(self.path) + if not os.path.exists(self.path): + msg = Translator.translate("Can't create '" + self.path + "' folder.\n") + FreeCAD.Console.PrintError(msg) + return False + + def saveData(self, ship, trim, drafts): + """ Write data file. + @param ship Selected ship instance + @param trim Trim in degrees. + @param drafts List of drafts to be performed. + @return True if error happens. + """ + # Open the file + filename = self.path + 'hydrostatics.dat' + try: + Output = open(filename, "w") + except IOError: + msg = Translator.translate("Can't write '" + filename + "' file.\n") + FreeCAD.Console.PrintError(msg) + return True + # Print header + Output.write(header) + Output.write(" #\n") + Output.write(" # File automatically exported by FreeCAD-Ship\n") + Output.write(" # This file contains transversal areas data, filled with following columns:\n") + Output.write(" # 1: Ship displacement [ton]\n") + Output.write(" # 2: Draft [m]\n") + Output.write(" # 3: Wetted surface [m2]\n") + Output.write(" # 4: 1cm triming ship moment [ton m]\n") + Output.write(" # 5: Bouyance center x coordinate\n") + Output.write(" #\n") + Output.write(" #################################################################\n") + # Print data + for i in range(0,len(drafts)): + draft = drafts[i] + point = Tools.Point(ship,draft,trim) + string = "%f %f %f %f %f\n" % (point.disp, point.draft, point.wet, point.mom, point.xcb) + Output.write(string) + # Close file + Output.close() + self.dataFile = filename + msg = Translator.translate("Data saved at '" + self.dataFile + "'.\n") + FreeCAD.Console.PrintMessage(msg) + return False + + def saveLayout(self, trim): + """ Prints the pyxplot layout. + @param trim Trim in degrees. + @return True if error happens. + """ + filename = self.path + 'volume.pyxplot' + # Open the file + try: + Output = open(filename, "w") + except IOError: + msg = Translator.translate("Can't write '" + filename + "' file.\n") + FreeCAD.Console.PrintError(msg) + return True + # Write header + Output.write(header) + Output.write(" #\n") + Output.write(" # File automatically exported by FreeCAD-Ship\n") + Output.write(" # This file contains a script to plot transversal areas curve.\n") + Output.write(" # To use it execute:\n") + Output.write(" #\n") + Output.write(" # pyxplot %s\n" % (filename)) + Output.write(" #\n") + Output.write(" #################################################################\n") + # Write general options for hydrostatics + Output.write("set numeric display latex\n") + Output.write("set output '%s'\n" % (self.path + 'volume.eps')) + Output.write("set title '$trim$ = %g [degrees]'\n" % (trim)) + Output.write("set key below\n") + Output.write("set grid\n") + # Configure axis + Output.write("# Y axis\n") + Output.write("set ylabel '$\\bigtriangleup$ / $\\mathrm{ton}$'\n") + Output.write("set ytic\n") + Output.write("# X axis\n") + Output.write("set xlabel '$Draft$ / $\\mathrm{m}$'\n") + Output.write("set xtic\n") + Output.write("set x2label '\\textit{Wetted area} / $\\mathrm{m}^2$'\n") + Output.write("set x2tic\n") + Output.write("set x3label '\\textit{1cm trim moment} / $\\mathrm{ton} \\times \\mathrm{m}$'\n") + Output.write("set x3tic\n") + Output.write("set x4label '$XCB$ / $\\mathrm{m}$'\n") + Output.write("set x4tic\n") + Output.write("set axis x2 top\n") + Output.write("set axis x4 top\n") + Output.write("# Line styles\n") + Output.write("set style 1 line linetype 1 linewidth 1 colour rgb (0):(0):(0)\n") + Output.write("set style 2 line linetype 1 linewidth 1 colour rgb (1):(0):(0)\n") + Output.write("set style 3 line linetype 1 linewidth 1 colour rgb (0):(0):(1)\n") + Output.write("set style 4 line linetype 1 linewidth 1 colour rgb (1):(0):(1)\n") + # Write plot call + Output.write("# Plot\n") + Output.write("plot '%s' using 2:1 title '$\\bigtriangleup$' axes x1y1 with lines style 1, \\\n" % (self.dataFile)) + Output.write(" '' using 3:1 title 'Wetted area' axes x2y1 with lines style 2, \\\n") + Output.write(" '' using 4:1 title '1cm trim moment' axes x3y1 with lines style 3, \\\n") + Output.write(" '' using 5:1 title 'XCB' axes x4y1 with lines style 4\n") + # Close file + self.layoutFile = filename + Output.close() + return False + + def execute(self): + """ Calls pyxplot in order to plot an save an image. + @return True if error happens. + """ + filename = self.path + 'volume' + comm = "pyxplot %s" % (self.layoutFile) + if os.system(comm): + msg = Translator.translate("Can't execute pyxplot. Maybe is not installed?\n") + FreeCAD.Console.PrintError(msg) + msg = Translator.translate("Plot will not generated\n") + FreeCAD.Console.PrintError(msg) + return True + comm = "gs -r300 -dEPSCrop -dTextAlphaBits=4 -sDEVICE=png16m -sOutputFile=%s.png -dBATCH -dNOPAUSE %s.eps" % (filename,filename) + if os.system(comm): + msg = Translator.translate("Can't execute ghostscript. Maybe is not installed?\n") + FreeCAD.Console.PrintError(msg) + msg = Translator.translate("Generated image will not converted to png\n") + FreeCAD.Console.PrintError(msg) + return True + return False diff --git a/src/Mod/Ship/shipHydrostatics/TaskPanel.py b/src/Mod/Ship/shipHydrostatics/TaskPanel.py index d0feb40dc..665e7edaf 100644 --- a/src/Mod/Ship/shipHydrostatics/TaskPanel.py +++ b/src/Mod/Ship/shipHydrostatics/TaskPanel.py @@ -28,7 +28,7 @@ import FreeCADGui as Gui # Qt library from PyQt4 import QtGui,QtCore # Module -# import Plot +import Plot import Instance from shipUtils import Paths, Translator from surfUtils import Geometry @@ -43,6 +43,13 @@ class TaskPanel: if not self.ship: return False self.save() + draft = self.form.minDraft.value() + drafts = [draft] + dDraft = (self.form.maxDraft.value() - self.form.minDraft.value())/self.form.nDraft.value() + for i in range(1,self.form.nDraft.value()): + draft = draft + dDraft + drafts.append(draft) + Plot.Plot(self.ship, self.form.trim.value(), drafts) return True def reject(self): diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 3792708c9..d3bf8698d 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -32,7 +32,7 @@ from shipUtils import Math def Displacement(ship, draft, trim): """ Calculate ship displacement. @param ship Selected ship instance - @param traft Draft. + @param draft Draft. @param trim Trim in degrees. @return [areas,disp,xcb]: \n areas : Area of each section \n @@ -174,3 +174,194 @@ def Displacement(ship, draft, trim): if vol > 0.0: xcb = moment / vol return [areas,disp,xcb] + +def WettedArea(ship, draft, trim): + """ Calculate wetted ship area. + @param ship Selected ship instance + @param draft Draft. + @param trim Trim in degrees. + @return Wetted ship area. + """ + angle = math.radians(trim) + sections = Instance.sections(ship) + xCoord = ship.xSection[:] + lines = [] + area = 0.0 + if not sections: + return 0.0 + for i in range(0, len(sections)): + # Get the section + section = sections[i] + if len(section) < 2: # Empty section + lines.append(0.0) + continue + # Get the position of the section + x = xCoord[i] + # Get the maximum Z value + Z = draft - x*math.tan(angle) + # Count the number of valid points + n = 0 + for j in range(0,len(section)): + z = section[j].z + if z > Z: + break + n = n+1 + # Discard invalid sections + if n == 0: + lines.append(0.0) + continue + # Truncate only valid points + points = section[0:n] + # Study if additional point is needed + if n < len(section): + y0 = section[n-1].y + z0 = section[n-1].z + y1 = section[n].y + z1 = section[n].z + if (Z > z0) and not (Math.isAprox(Z,z0)): + factor = (Z - z0) / (z1 - z0) + y = y0 + factor*(y1 - y0) + points.append(App.Base.Vector(x,y,Z)) + # Convert into array with n elements (Number of points by sections) + # with m elements into them (Number of points with the same height, + # typical of multibody) + section = [] + nPoints = 0 + j = 0 + while j < len(points)-1: + section.append([points[j]]) + k = j+1 + while(Math.isAprox(points[j].z, points[k].z)): + section[nPoints].append(points[k]) + k = k+1 + nPoints = nPoints + 1 + j = k + # Integrate line area + line = 0.0 + for j in range(0, len(section)-1): + for k in range(0, min(len(section[j])-1, len(section[j+1])-1)): + # y11,z11 ------- y01,z01 + # | | + # | | + # | | + # y10,z10 ------- y00,z00 + y00 = abs(section[j][k].y) + z00 = section[j][k].z + y10 = abs(section[j][k+1].y) + z10 = section[j][k+1].z + y01 = abs(section[j+1][k].y) + z01 = section[j+1][k].z + y11 = abs(section[j+1][k+1].y) + z11 = section[j+1][k+1].z + dy = y11 - y10 + dz = z11 - z10 + line = line + math.sqrt(dy*dy + dz*dz) + dy = y01 - y00 + dz = z01 - z00 + line = line + math.sqrt(dy*dy + dz*dz) + if(len(section[j]) < len(section[j+1])): + # y01,z01 ------- y11,z11 + # | __/ + # | __/ + # | / + # y00,z00 + k = len(section[j])-1 + y00 = abs(section[j][k].y) + z00 = section[j][k].z + y01 = abs(section[j+1][k].y) + z01 = section[j+1][k].z + y11 = abs(section[j+1][k+1].y) + z11 = section[j+1][k+1].z + dy = y11 - y00 + dz = z11 - z00 + line = line + math.sqrt(dy*dy + dz*dz) + dy = y01 - y00 + dz = z01 - z00 + line = line + math.sqrt(dy*dy + dz*dz) + elif(len(section[j]) > len(section[j+1])): + # y01,z01 + # | \__ + # | \__ + # | \ + # y00,z00 ------- y10,z10 + k = len(section[j+1])-1 + y00 = abs(section[j][k].y) + z00 = section[j][k].z + y10 = abs(section[j][k+1].y) + z10 = section[j][k+1].z + y01 = abs(section[j+1][k].y) + z01 = section[j+1][k].z + dy = y01 - y00 + dz = z01 - z00 + line = line + math.sqrt(dy*dy + dz*dz) + dy = y01 - y10 + dz = z01 - z10 + line = line + math.sqrt(dy*dy + dz*dz) + elif(len(section[j]) == 1): + # y1,z1 ------- + # | + # | + # | + # y0,z0 ------- + k = 0 + y0 = abs(section[j][k].y) + z0 = section[j][k].z + y1 = abs(section[j+1][k].y) + z1 = section[j+1][k].z + dy = y1 - y0 + dz = z1 - z0 + line = line + math.sqrt(dy*dy + dz*dz) + lines.append(2.0*line) # 2x because only half ship is represented + # Add area if proceed + if i > 0: + dx = xCoord[i] - xCoord[i-1] + x = 0.5*(xCoord[i] + xCoord[i-1]) + line = 0.5*(lines[i] + lines[i-1]) + area = area + line*dx + return area + +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. + """ + angle = math.degrees(math.atan2(0.01,0.5*ship.Length)) + newTrim = trim + angle + data = Displacement(ship,draft,newTrim) + mom0 = -disp*xcb + mom1 = -data[1]*data[2] + return mom1 - mom0 + +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]. + @note Moment is positive when produce positive trim. + """ + def __init__(self, ship, draft, trim): + """ Use all hydrostatics tools to define a hydrostatics + point. + @param ship Selected ship instance + @param draft Draft. + @param trim Trim in degrees. + """ + # Hydrostatics computation + areasData = Displacement(ship,draft,trim) + wettedArea = WettedArea(ship,draft,trim) + moment = Moment(ship,draft,trim,areasData[1],areasData[2]) + # Store final data + self.draft = draft + self.trim = trim + self.disp = areasData[1] + self.xcb = areasData[2] + self.wet = wettedArea + self.mom = moment