423 lines
16 KiB
Python
423 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 math
|
|
# FreeCAD modules
|
|
from FreeCAD import Vector, Part
|
|
import FreeCAD as App
|
|
import FreeCADGui as Gui
|
|
# Module
|
|
import Instance
|
|
from shipUtils import Math
|
|
|
|
def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30):
|
|
""" Compute 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)
|
|
"""
|
|
if n < 2:
|
|
return []
|
|
# We will take a duplicate of ship shape in order to place it
|
|
shape = ship.Shape.copy()
|
|
shape.translate(Vector(0.0,0.0,-draft))
|
|
# Rotations composition is Roll->Trim->Yaw
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(1.0,0.0,0.0), roll)
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,0.0,1.0), yaw)
|
|
# Now we need to know the x range of values
|
|
bbox = shape.BoundBox
|
|
xmin = bbox.XMin
|
|
xmax = bbox.XMax
|
|
dx = (xmax - xmin) / (n-1.0)
|
|
# First area is equal to zero.
|
|
areas = [[xmin, 0.0]]
|
|
# Since we need face entities, in order to compute sections we will
|
|
# create boxes with front face at transversal area position,
|
|
# compute solid common, divide by faces, and preserve only desired
|
|
# ones.
|
|
App.Console.PrintMessage("Computing transversal areas...\n")
|
|
App.Console.PrintMessage("Some Inventor representation errors can be shown, ignore it please.\n")
|
|
for i in range(1,n-1):
|
|
App.Console.PrintMessage("%d / %d\n" % (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 - 1.0)
|
|
box = Part.makeBox(1.5*L + x, 3.0*B, - bbox.ZMin + 1.0, p)
|
|
# Compute common part with ship
|
|
for s in shape.Solids:
|
|
# Get solids intersection
|
|
try:
|
|
common = box.common(s)
|
|
except:
|
|
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:
|
|
continue
|
|
# Divide by faces and compute only section 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
|
|
# Valid face, compute area
|
|
area = area + f.Area
|
|
# Destroy last object generated
|
|
App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name)
|
|
# Append transversal area
|
|
areas.append([x, area])
|
|
# Last area is equal to zero
|
|
areas.append([xmax, 0.0])
|
|
App.Console.PrintMessage("Done!\n")
|
|
return areas
|
|
|
|
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0):
|
|
""" Compute 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 FreeCAD.Vector class.
|
|
@note Returned Bouyance center is in non modified ship coordinates
|
|
"""
|
|
# We will take a duplicate of ship shape in order to place it
|
|
shape = ship.Shape.copy()
|
|
shape.translate(Vector(0.0,0.0,-draft))
|
|
# Rotations composition is Roll->Trim->Yaw
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(1.0,0.0,0.0), roll)
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,0.0,1.0), yaw)
|
|
# Now we need to know box dimensions
|
|
bbox = shape.BoundBox
|
|
xmin = bbox.XMin
|
|
xmax = bbox.XMax
|
|
# Create the box
|
|
L = xmax - xmin
|
|
B = bbox.YMax - bbox.YMin
|
|
p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
|
|
box = Part.makeBox(3.0*L, 3.0*B, -bbox.ZMin + 1.0, p)
|
|
vol = 0.0
|
|
cog = Vector()
|
|
for solid in shape.Solids:
|
|
# Compute common part with ship
|
|
try:
|
|
common = box.common(solid)
|
|
except:
|
|
continue
|
|
# Get data
|
|
vol = vol + common.Volume
|
|
for s in common.Solids:
|
|
sCoG = s.CenterOfMass
|
|
cog.x = cog.x + sCoG.x*s.Volume
|
|
cog.y = cog.y + sCoG.y*s.Volume
|
|
cog.z = cog.z + sCoG.z*s.Volume
|
|
cog.x = cog.x / vol
|
|
cog.y = cog.y / vol
|
|
cog.z = cog.z / vol
|
|
Vol = L*B*abs(bbox.ZMin)
|
|
# Undo transformations
|
|
B = Vector()
|
|
B.x = cog.x*math.cos(math.radians(-yaw)) - cog.y*math.sin(math.radians(-yaw))
|
|
B.y = cog.x*math.sin(math.radians(-yaw)) + cog.y*math.cos(math.radians(-yaw))
|
|
B.z = cog.z
|
|
cog.x = B.x*math.cos(math.radians(-trim)) - B.z*math.sin(math.radians(-trim))
|
|
cog.y = B.y
|
|
cog.z = B.x*math.sin(math.radians(-trim)) + B.z*math.cos(math.radians(-trim))
|
|
B.x = cog.x
|
|
B.y = cog.y*math.cos(math.radians(-roll)) - cog.z*math.sin(math.radians(-roll))
|
|
B.z = cog.y*math.sin(math.radians(-roll)) + cog.z*math.cos(math.radians(-roll))
|
|
B.z = B.z + draft
|
|
# Return data
|
|
dens = 1.025 # [tons/m3], salt water
|
|
return [dens*vol, B, vol/Vol]
|
|
|
|
def wettedArea(shape, draft, trim):
|
|
""" Calculate wetted ship area.
|
|
@param shape Ship external faces instance.
|
|
@param draft Draft.
|
|
@param trim Trim in degrees.
|
|
@return Wetted ship area.
|
|
"""
|
|
area = 0.0
|
|
nObjects = 0
|
|
# We will take a duplicate of ship shape in order to place it
|
|
shape = shape.copy()
|
|
shape.translate(Vector(0.0,0.0,-draft))
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
|
|
# Now we need to know the x range of values
|
|
bbox = shape.BoundBox
|
|
xmin = bbox.XMin
|
|
xmax = bbox.XMax
|
|
# Create the box
|
|
L = xmax - xmin
|
|
B = bbox.YMax - bbox.YMin
|
|
p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
|
|
box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p)
|
|
# Compute common part with ship
|
|
for f in shape.Faces:
|
|
# Get solids intersection
|
|
try:
|
|
common = box.common(f)
|
|
except:
|
|
continue
|
|
area = area + common.Area
|
|
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,0.0,newTrim,0.0)
|
|
mom0 = -disp*xcb
|
|
mom1 = -data[0]*data[1].x
|
|
return mom1 - mom0
|
|
|
|
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
|
|
# We will take a duplicate of ship shape in order to place it
|
|
shape = ship.Shape.copy()
|
|
shape.translate(Vector(0.0,0.0,-draft))
|
|
shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim)
|
|
# Now we need to know the x range of values
|
|
bbox = shape.BoundBox
|
|
xmin = bbox.XMin
|
|
xmax = bbox.XMax
|
|
# Create the box
|
|
L = xmax - xmin
|
|
B = bbox.YMax - bbox.YMin
|
|
p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
|
|
box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p)
|
|
# Compute common part with ship
|
|
for s in shape.Solids:
|
|
# Get solids intersection
|
|
try:
|
|
common = box.common(s)
|
|
except:
|
|
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:
|
|
continue
|
|
# Divide by faces and compute only section placed ones
|
|
faces = common.Faces
|
|
for f in faces:
|
|
faceBounds = f.BoundBox
|
|
# Orientation filter
|
|
if faceBounds.ZMax - faceBounds.ZMin > 0.00001:
|
|
continue
|
|
# Place filter
|
|
if abs(faceBounds.ZMax) > 0.00001:
|
|
continue
|
|
# Valid face, compute area
|
|
area = area + f.Area
|
|
maxX = max(maxX, faceBounds.XMax)
|
|
minX = max(minX, faceBounds.XMin)
|
|
maxY = max(maxY, faceBounds.YMax)
|
|
minY = max(minY, faceBounds.YMin)
|
|
# Destroy last object generated
|
|
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]
|
|
|
|
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].
|
|
"""
|
|
nRoll = 2
|
|
maxRoll = 7.0
|
|
B0 = displacement(ship,draft,0.0,trim,0.0)[1]
|
|
BM = 0.0
|
|
for i in range(0,nRoll):
|
|
roll = (maxRoll/nRoll)*(i+1)
|
|
B1 = displacement(ship,draft,roll,trim,0.0)[1]
|
|
# * M
|
|
# / \
|
|
# / \ BM ==|> BM = (BB/2) / tan(alpha/2)
|
|
# / \
|
|
# *-------*
|
|
# BB
|
|
BB = [B1.x - B0.x, B1.y - B0.y]
|
|
BB = math.sqrt(BB[0]*BB[0] + BB[1]*BB[1])
|
|
BM = BM + 0.5*BB/math.tan(math.radians(0.5*roll)) / nRoll # nRoll is the weight function
|
|
return BM
|
|
|
|
def mainFrameCoeff(ship, draft):
|
|
""" Calculate main frame coefficient.
|
|
@param ship Selected ship instance
|
|
@param draft Draft.
|
|
@return Main frame coefficient
|
|
"""
|
|
cm = 0.0
|
|
maxY = 0.0
|
|
minY = 0.0
|
|
# We will take a duplicate of ship shape in order to place it
|
|
shape = ship.Shape.copy()
|
|
shape.translate(Vector(0.0,0.0,-draft))
|
|
x = 0.0
|
|
area = 0.0
|
|
# Now we need to know the x range of values
|
|
bbox = shape.BoundBox
|
|
xmin = bbox.XMin
|
|
xmax = bbox.XMax
|
|
# Create the box
|
|
L = xmax - xmin
|
|
B = bbox.YMax - bbox.YMin
|
|
p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0)
|
|
box = Part.makeBox(1.5*L + x, 3.0*B, - bbox.ZMin + 1.0, p)
|
|
# Compute common part with ship
|
|
for s in shape.Solids:
|
|
# Get solids intersection
|
|
try:
|
|
common = box.common(s)
|
|
except:
|
|
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:
|
|
continue
|
|
# Divide by faces and compute only section 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
|
|
# Valid face, compute area
|
|
area = area + f.Area
|
|
maxY = max(maxY, faceBounds.YMax)
|
|
minY = max(minY, faceBounds.YMin)
|
|
# Destroy last object generated
|
|
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.
|
|
"""
|
|
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.
|
|
"""
|
|
# Hydrostatics computation
|
|
dispData = displacement(ship,draft,0.0,trim,0.0)
|
|
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)
|
|
# Store final data
|
|
self.draft = draft
|
|
self.trim = trim
|
|
self.disp = dispData[0]
|
|
self.xcb = dispData[1].x
|
|
self.wet = wet
|
|
self.farea = farea[0]
|
|
self.mom = mom
|
|
self.KBt = dispData[1].y
|
|
self.BMt = bm
|
|
self.Cb = dispData[2]
|
|
self.Cf = farea[1]
|
|
self.Cm = cm
|