Files
create/src/Mod/Ship/shipHydrostatics/Plot.py
2012-09-07 11:13:13 +02:00

346 lines
16 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import os
import math
# FreeCAD modules
import FreeCAD,FreeCADGui
from FreeCAD import Base, Vector
import Part, 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')
ImageGui.open(self.path + 'stability.png')
ImageGui.open(self.path + 'coeffs.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(" # 6: Floating area\n")
Output.write(" # 7: KBt\n")
Output.write(" # 8: BMt\n")
Output.write(" # 9: Cb (block coefficient)\n")
Output.write(" # 10: Cf (Floating coefficient)\n")
Output.write(" # 11: Cm (Main frame coefficient)\n")
Output.write(" #\n")
Output.write(" #################################################################\n")
# Get external faces
faces = self.externalFaces(ship.Shape)
if len(faces) == 0:
msg = Translator.translate("Can't detect external faces from ship object.\n")
FreeCAD.Console.PrintError(msg)
else:
faces = Part.makeShell(faces)
# Print data
FreeCAD.Console.PrintMessage("Computing hydrostatics...\n")
for i in range(0,len(drafts)):
FreeCAD.Console.PrintMessage("\t%d / %d\n" % (i+1, len(drafts)))
draft = drafts[i]
point = Tools.Point(ship,faces,draft,trim)
string = "%f %f %f %f %f %f %f %f %f %f %f\n" % (point.disp, point.draft, point.wet, point.mom, point.xcb, point.farea, point.KBt, point.BMt, point.Cb, point.Cf, point.Cm)
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 (0.1):(0.5):(0.1)\n")
# Write plot call
Output.write("# Plot\n")
Output.write("plot '%s' using 2:1 title 'Draft' 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")
# Prepare second plot
Output.write("set output '%s'\n" % (self.path + 'stability.eps'))
Output.write("# X axis\n")
Output.write("set x2label '\\textit{Floating area} / $\\mathrm{m}^2$'\n")
Output.write("set x2tic\n")
Output.write("set x3label '$KB_{T}$ / $\\mathrm{m}$'\n")
Output.write("set x3tic\n")
Output.write("set x4label '$BM_{T}$ / $\\mathrm{m}$'\n")
Output.write("set x4tic\n")
# Write plot call
Output.write("# Plot\n")
Output.write("plot '%s' using 2:1 title 'Draft' axes x1y1 with lines style 1, \\\n" % (self.dataFile))
Output.write(" '' using 6:1 title 'Floating area' axes x2y1 with lines style 2, \\\n")
Output.write(" '' using 7:1 title '$KB_{T}$' axes x3y1 with lines style 3, \\\n")
Output.write(" '' using 8:1 title '$BM_{T}$' axes x4y1 with lines style 4\n")
# Prepare third plot
Output.write("set output '%s'\n" % (self.path + 'coeffs.eps'))
Output.write("# X axis\n")
Output.write("set x2label '$C_{B}$'\n")
Output.write("set x2tic\n")
Output.write("set x3label '$C_{F}$'\n")
Output.write("set x3tic\n")
Output.write("set x4label '$C_{M}$'\n")
Output.write("set x4tic\n")
# Write plot call
Output.write("# Plot\n")
Output.write("plot '%s' using 2:1 title 'Draft' axes x1y1 with lines style 1, \\\n" % (self.dataFile))
Output.write(" '' using 9:1 title '$C_{B}$' axes x2y1 with lines style 2, \\\n")
Output.write(" '' using 10:1 title '$C_{F}$' axes x3y1 with lines style 3, \\\n")
Output.write(" '' using 11:1 title '$C_{M}$' 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.
"""
# Plot
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
# Convert volume image
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
# Convert stability image
filename = self.path + 'stability'
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
# Convert coefficients image
filename = self.path + 'coeffs'
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
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
"""
# Get initial data
result = []
vertexes = line.Vertexes
nVertex = len(vertexes)
# Perform the cut
section = line.cut(surface)
# Filter all old points
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)
FreeCAD.Console.PrintMessage("Computing external faces...\n")
# Valid/unvalid faces detection loop
for i in range(0,len(faces)):
FreeCAD.Console.PrintMessage("\t%d / %d\n" % (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(0,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(0,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)
return result