FreeCAD/src/Mod/Ship/shipHydrostatics/Tools.py
2017-02-27 11:32:47 +01:00

535 lines
18 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2011, 2016 *
#* 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
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 = Units.parseQuantity("1025 kg/m^3") # 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 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:
# 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, base_z
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
"""
# 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 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(
"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)
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 []
if draft is None:
draft = ship.Draft
shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim)
shape = getUnderwaterSide(shape)
# Sections distance computation
bbox = shape.BoundBox
xmin = bbox.XMin
xmax = bbox.XMax
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 = [(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
# ones.
App.Console.PrintMessage("Computing transversal areas...\n")
App.Console.PrintMessage("Some Inventor representation errors can be"
" shown, please ignore them.\n")
for i in range(1, n - 1):
App.Console.PrintMessage("{0} / {1}\n".format(i, n - 2))
x = xmin + i * dx
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)
App.Console.PrintError(msg + '\n')
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((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((Units.Quantity(xmax, Units.Length),
Units.Quantity(0.0, Units.Area)))
App.Console.PrintMessage("Done!\n")
return areas
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 referred to the original ship position.
"""
if draft is None:
draft = ship.Draft
shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim)
shape = getUnderwaterSide(shape)
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
bbox = shape.BoundBox
Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin)
# 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.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(roll.getValueAs("rad")),
base_z))
m.rotateX(-roll.getValueAs("rad"))
B.transform(m)
try:
cb = vol / Vol
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null volume found during the displacement"
" computation!",
None)
App.Console.PrintError(msg + '\n')
cb = 0.0
# 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
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=None,
roll=Units.parseQuantity("0 deg"),
trim=Units.parseQuantity("0 deg")):
"""Compute the ship floating area
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 want to 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)
App.Console.PrintError(msg + '\n')
area = Units.Quantity(0.0, Units.Area)
bbox = shape.BoundBox
Area = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin)
try:
cf = area.Value / Area
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null area found during the floating area"
" computation!",
None)
App.Console.PrintError(msg + '\n')
cf = 0.0
return area, cf
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 = Units.parseQuantity("7 deg")
BM = 0.0
for i in range(nRoll):
roll = (maxRoll / nRoll) * (i + 1)
_, B1, _ = displacement(ship, draft, roll, trim)
# * M
# / \
# / \ BM ==|> BM = (BB/2) / sin(alpha/2)
# / \
# *-------*
# BB
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=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
"""
if draft is None:
draft = ship.Draft
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)
App.Console.PrintError(msg + '\n')
area = 0.0
bbox = shape.BoundBox
Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin)
try:
cm = area / Area
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null area found during the main frame area"
" coefficient computation!",
None)
App.Console.PrintError(msg + '\n')
cm = 0.0
return cm
class Point:
"""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):
"""Compute all the hydrostatics.
Position argument:
ship -- Ship instance
faces -- Ship external faces
draft -- Ship draft
trim -- Trim angle
"""
disp, B, cb = displacement(ship, draft=draft, trim=trim)
if not faces:
wet = 0.0
else:
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 = disp
self.xcb = Units.Quantity(B.x, Units.Length)
self.wet = wet
self.farea = farea
self.mom = mom
self.KBt = Units.Quantity(B.z, Units.Length)
self.BMt = bm
self.Cb = cb
self.Cf = cf
self.Cm = cm