Added all the Hydrostatics to the console interface

This commit is contained in:
Jose Luis Cercos Pita 2016-01-22 13:17:13 +01:00
parent c65dbe1db1
commit 0a4a8191a2
8 changed files with 371 additions and 442 deletions

View File

@ -30,4 +30,5 @@ __doc__="The Ships module provide a set of tools to make some specific Naval" \
" Architecture computations"
from shipCreateShip.Tools import createShip
from shipHydrostatics.Tools import areas
from shipHydrostatics.Tools import areas, displacement, wettedArea, moment,
floatingArea, BMT, mainFrameCoeff

View File

@ -89,9 +89,9 @@ class Plot(object):
addInfo = ("$XCB = {0} \\; \\mathrm{{m}}$\n"
"$Area_{{max}} = {1} \\; \\mathrm{{m}}^2$\n"
"$\\bigtriangleup = {2} \\; \\mathrm{{tons}}$".format(
xcb,
xcb.getValueAs("m").Value,
maxArea,
disp))
disp.getValueAs("kg").Value / 1000.0))
ax.text(0.0,
0.01 * maxArea,
addInfo,

View File

@ -55,11 +55,11 @@ class Preview(object):
point = Base.Vector(x, y, 0.0)
plane = Part.makePlane(L, B, point, Base.Vector(0, 0, 1))
plane.rotate(Base.Vector(0, 0, 0), Base.Vector(0, 1, 0), trim)
plane.translate(Base.Vector(0, 0, draft * Units.Metre.Value))
plane.translate(Base.Vector(0, 0, draft))
Part.show(plane)
objs = FreeCAD.ActiveDocument.Objects
self.obj = objs[len(objs) - 1]
self.obj.Label = 'FreeSurface'
self.obj.Label = 'FreeSurfaceHelper'
guiObj = FreeCADGui.ActiveDocument.getObject(self.obj.Name)
guiObj.ShapeColor = (0.4, 0.8, 0.85)
guiObj.Transparency = 50
@ -69,4 +69,4 @@ class Preview(object):
if not self.obj:
return
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
self.obj = None
self.obj = None

View File

@ -55,12 +55,11 @@ class TaskPanel:
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
num = form.num.value()
data = Hydrostatics.displacement(self.ship,
draft.getValueAs("m").Value,
0.0,
trim.getValueAs("deg").Value)
disp = data[0]
xcb = data[1].x
disp, B, _ = Hydrostatics.displacement(self.ship,
draft,
Units.parseQuantity("0 deg"),
trim)
xcb = Units.Quantity(B.x, Units.Length)
data = Hydrostatics.areas(self.ship,
num,
draft=draft,
@ -245,24 +244,11 @@ class TaskPanel:
None,
QtGui.QApplication.UnicodeUTF8))
def clampLength(self, widget, val_min, val_max, val):
if val >= val_min and val <= val_max:
def clampValue(self, widget, val_min, val_max, val):
if val_min <= 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)))
widget.setText(val.UserString)
return val
def onData(self, value):
@ -279,32 +265,24 @@ class TaskPanel:
# Get the values (or fix them in bad setting case)
try:
draft = Units.Quantity(Locale.fromString(
form.draft.text())).getValueAs('m').Value
draft = Units.parseQuantity(Locale.fromString(form.draft.text()))
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)))
draft = self.ship.Draft
form.draft.setText(draft.UserString)
try:
trim = Units.Quantity(Locale.fromString(
form.trim.text())).getValueAs('deg').Value
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
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)))
trim = Units.parseQuantity("0 deg")
form.trim.setText(trim.UserString)
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)
draft_min = Units.Quantity(bbox.ZMin, Units.Length)
draft_max = Units.Quantity(bbox.ZMax, Units.Length)
draft = self.clampValue(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)
trim_min = Units.parseQuantity("-180 deg")
trim_max = Units.parseQuantity("180 deg")
trim = self.clampValue(form.trim, trim_min, trim_max, trim)
self.onUpdate()
self.preview.update(draft, trim, self.ship)
@ -319,40 +297,39 @@ class TaskPanel:
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
draft = Units.parseQuantity(Locale.fromString(form.draft.text()))
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
# Calculate the drafts at each perpendicular
angle = math.radians(trim)
angle = trim.getValueAs("rad").Value
L = self.ship.Length.getValueAs('m').Value
B = self.ship.Breadth.getValueAs('m').Value
draftAP = draft + 0.5 * L * math.tan(angle)
draftAP = draft + 0.5 * self.ship.Length * math.tan(angle)
if draftAP < 0.0:
draftAP = 0.0
draftFP = draft - 0.5 * L * math.tan(angle)
draftFP = draft - 0.5 * self.ship.Length * math.tan(angle)
if draftFP < 0.0:
draftFP = 0.0
# Calculate the involved hydrostatics
data = Hydrostatics.displacement(self.ship,
draft,
0.0,
trim)
disp, B, _ = Hydrostatics.displacement(self.ship,
draft,
Units.parseQuantity("0 deg"),
trim)
xcb = Units.Quantity(B.x, Units.Length)
# Setup the html string
string = 'L = {0} [m]<BR>'.format(L)
string = string + 'B = {0} [m]<BR>'.format(B)
string = string + 'T = {0} [m]<HR>'.format(draft)
string = string + 'Trim = {0} [degrees]<BR>'.format(trim)
string = string + 'T<sub>AP</sub> = {0} [m]<BR>'.format(draftAP)
string = string + 'T<sub>FP</sub> = {0} [m]<HR>'.format(draftFP)
string = u'L = {0}<BR>'.format(self.ship.Length.UserString)
string += u'B = {0}<BR>'.format(self.ship.Breadth.UserString)
string += u'T = {0}<HR>'.format(draft.UserString)
string += u'Trim = {0}<BR>'.format(trim.UserString)
string += u'T<sub>AP</sub> = {0}<BR>'.format(draftAP.UserString)
string += u'T<sub>FP</sub> = {0}<HR>'.format(draftFP.UserString)
dispText = QtGui.QApplication.translate(
"ship_areas",
'Displacement',
None,
QtGui.QApplication.UnicodeUTF8)
string = string + dispText + ' = {0} [ton]<BR>'.format(data[0])
string = string + 'XCB = {0} [m]'.format(data[1].x)
string += dispText + u' = {0}<BR>'.format(disp.UserString)
string += u'XCB = {0}'.format(xcb.UserString)
form.output.setHtml(string)
def save(self):
@ -363,10 +340,8 @@ class TaskPanel:
form.trim = self.widget(QtGui.QLineEdit, "Trim")
form.num = self.widget(QtGui.QSpinBox, "Num")
draft = Units.Quantity(Locale.fromString(
form.draft.text())).getValueAs('m').Value
trim = Units.Quantity(Locale.fromString(
form.trim.text())).getValueAs('deg').Value
draft = Units.parseQuantity(Locale.fromString(form.draft.text()))
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
num = form.num.value()
props = self.ship.PropertiesList
@ -385,7 +360,7 @@ class TaskPanel:
"AreaCurveDraft",
"Ship",
tooltip)
self.ship.AreaCurveDraft = '{} m'.format(draft)
self.ship.AreaCurveDraft = draft
try:
props.index("AreaCurveTrim")
except ValueError:
@ -401,7 +376,7 @@ class TaskPanel:
"AreaCurveTrim",
"Ship",
tooltip)
self.ship.AreaCurveTrim = '{} deg'.format(trim)
self.ship.AreaCurveTrim = trim
try:
props.index("AreaCurveNum")
except ValueError:
@ -426,4 +401,4 @@ def createTask():
if panel.setupUi():
Gui.Control.closeDialog(panel)
return None
return panel
return panel

View File

@ -121,8 +121,12 @@ def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True):
for i in range(MAX_EQUILIBRIUM_ITERS):
# Get the displacement, and the bouyance application point
disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim)
disp *= 1000.0 * G
disp, B, _ = Hydrostatics.displacement(ship,
draft * Units.Metre,
roll * Units.Degree,
trim * Units.Degree)
disp = disp.getValueAs("kg").Value * G
B.multiply(1.0 / Units.Metre.Value)
# Add the tanks effect on the center of gravity
cog = Vector(COG.x * W, COG.y * W, COG.z * W)
for i,t in enumerate(tanks):

View File

@ -84,11 +84,11 @@ class Plot(object):
t1cm = []
xcb = []
for i in range(len(self.points)):
disp.append(self.points[i].disp)
draft.append(self.points[i].draft)
warea.append(self.points[i].wet)
t1cm.append(self.points[i].mom)
xcb.append(self.points[i].xcb)
disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0)
draft.append(self.points[i].draft.getValueAs("m").Value)
warea.append(self.points[i].wet.getValueAs("m^2").Value)
t1cm.append(self.points[i].mom.getValueAs("kg*m").Value / 1000.0)
xcb.append(self.points[i].xcb.getValueAs("m").Value)
axes = Plot.axesList()
for ax in axes:
@ -166,11 +166,11 @@ class Plot(object):
kbt = []
bmt = []
for i in range(len(self.points)):
disp.append(self.points[i].disp)
draft.append(self.points[i].draft)
farea.append(self.points[i].farea)
kbt.append(self.points[i].KBt)
bmt.append(self.points[i].BMt)
disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0)
draft.append(self.points[i].draft.getValueAs("m").Value)
farea.append(self.points[i].farea.getValueAs("m^2").Value)
kbt.append(self.points[i].KBt.getValueAs("m").Value)
bmt.append(self.points[i].BMt.getValueAs("m").Value)
axes = Plot.axesList()
for ax in axes:
@ -248,8 +248,8 @@ class Plot(object):
cf = []
cm = []
for i in range(len(self.points)):
disp.append(self.points[i].disp)
draft.append(self.points[i].draft)
disp.append(self.points[i].disp.getValueAs("kg").Value / 1000.0)
draft.append(self.points[i].draft.getValueAs("m").Value)
cb.append(self.points[i].Cb)
cf.append(self.points[i].Cf)
cm.append(self.points[i].Cm)
@ -322,17 +322,28 @@ class Plot(object):
# Print the data
for i in range(len(self.points)):
point = self.points[i]
s.set("A{}".format(i + 2), str(point.disp))
s.set("B{}".format(i + 2), str(point.draft))
s.set("C{}".format(i + 2), str(point.wet))
s.set("D{}".format(i + 2), str(point.mom))
s.set("E{}".format(i + 2), str(point.farea))
s.set("F{}".format(i + 2), str(point.xcb))
s.set("G{}".format(i + 2), str(point.KBt))
s.set("H{}".format(i + 2), str(point.BMt))
s.set("I{}".format(i + 2), str(point.Cb))
s.set("J{}".format(i + 2), str(point.Cf))
s.set("K{}".format(i + 2), str(point.Cm))
s.set("A{}".format(i + 2),
str(point.disp.getValueAs("kg").Value / 1000.0))
s.set("B{}".format(i + 2),
str(point.draft.getValueAs("m").Value))
s.set("C{}".format(i + 2),
str(point.wet.getValueAs("m^2").Value))
s.set("D{}".format(i + 2),
str(point.mom.getValueAs("kg*m").Value / 1000.0))
s.set("E{}".format(i + 2),
str(point.farea.getValueAs("m^2").Value))
s.set("F{}".format(i + 2),
str(point.xcb.getValueAs("m").Value))
s.set("G{}".format(i + 2),
str(point.KBt.getValueAs("m").Value))
s.set("H{}".format(i + 2),
str(point.BMt.getValueAs("m").Value))
s.set("I{}".format(i + 2),
str(point.Cb))
s.set("J{}".format(i + 2),
str(point.Cf))
s.set("K{}".format(i + 2),
str(point.Cm))
# Recompute
FreeCAD.activeDocument().recompute()

View File

@ -57,12 +57,9 @@ class TaskPanel:
form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft")
form.nDraft = self.widget(QtGui.QSpinBox, "NDraft")
trim = Units.Quantity(Locale.fromString(
form.trim.text())).getValueAs('deg').Value
min_draft = Units.Quantity(Locale.fromString(
form.minDraft.text())).getValueAs('m').Value
max_draft = Units.Quantity(Locale.fromString(
form.maxDraft.text())).getValueAs('m').Value
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
min_draft = Units.parseQuantity(Locale.fromString(form.minDraft.text()))
max_draft = Units.parseQuantity(Locale.fromString(form.maxDraft.text()))
n_draft = form.nDraft.value()
draft = min_draft
@ -72,7 +69,6 @@ class TaskPanel:
draft = draft + dDraft
drafts.append(draft)
# Compute data
# Get external faces
self.loop = QtCore.QEventLoop()
self.timer = QtCore.QTimer()
@ -94,6 +90,7 @@ class TaskPanel:
App.Console.PrintError(msg + '\n')
return False
faces = Part.makeShell(faces)
# Get the hydrostatics
msg = QtGui.QApplication.translate(
"ship_console",

View File

@ -34,7 +34,7 @@ from shipUtils import Math
import shipUtils.Units as USys
DENS = 1.025 # [tons/m3], salt water
DENS = Units.parseQuantity("1025 kg/m^3") # Salt water
COMMON_BOOLEAN_ITERATIONS = 10
@ -49,9 +49,11 @@ def placeShipShape(shape, draft, roll, trim):
roll -- Roll angle
trim -- Trim angle
Returned value:
The same transformed input shape. Just for debugging purposes, you can
discard it.
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:
@ -66,16 +68,21 @@ def placeShipShape(shape, draft, roll, trim):
shape.translate(Vector(draft * math.sin(math.radians(trim)), 0.0, 0.0))
shape.translate(Vector(0.0, 0.0, -draft))
return shape
return shape, base_z
def getUnderwaterSide(shape):
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
"""
@ -109,7 +116,7 @@ def getUnderwaterSide(shape):
"UnderwaterSideHelper")
common.Shapes = [orig, box]
App.ActiveDocument.recompute()
if len(common.Shape.Solids) == 0:
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(
@ -139,7 +146,7 @@ def getUnderwaterSide(shape):
def areas(ship, n, draft=None,
roll=Units.parseQuantity("0 deg"),
trim=Units.parseQuantity("0 deg")):
""" Compute the ship transversal areas
"""Compute the ship transversal areas
Position arguments:
ship -- Ship object (see createShip)
@ -161,11 +168,8 @@ def areas(ship, n, draft=None,
if draft is None:
draft = ship.Draft
# Manipulate a copy of the ship shape
shape = getUnderwaterSide(placeShipShape(ship.Shape.copy(),
draft,
roll,
trim))
shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim)
shape = getUnderwaterSide(shape)
# Sections distance computation
bbox = shape.BoundBox
@ -191,6 +195,12 @@ def areas(ship, n, draft=None,
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,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
areas.append((Units.Quantity(x, Units.Length),
Units.Quantity(0.0, Units.Area)))
continue
@ -204,397 +214,328 @@ def areas(ship, n, draft=None,
return areas
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0):
""" Compute the 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 a FreeCAD.Vector instance.
@note Returned Bouyance center is in the non modified ship coordinates
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 refered to the original ship position.
"""
# We will take a duplicate of ship shape in order to conviniently
# manipulate it
shape = ship.Shape.copy()
_draft = draft * Units.Metre.Value
# 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 and yaw 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.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw)
shape.translate(Vector(0.0, 0.0, -_draft))
Part.show(shape)
ship_shape = App.ActiveDocument.Objects[-1]
if draft is None:
draft = ship.Draft
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",
"DisplacementHelper")
common.Shapes = [ship_shape, box]
App.ActiveDocument.recompute()
if 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. The tool is retrying that slightly"
" moving the free surface position",
None,
QtGui.QApplication.UnicodeUTF8)
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()
shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim)
shape = getUnderwaterSide(shape)
vol = 0.0
cog = Vector()
if len(common.Shape.Solids) > 0:
for solid in common.Shape.Solids:
vol += solid.Volume / Units.Metre.Value**3
if len(shape.Solids) > 0:
for solid in shape.Solids:
vol += solid.Volume
sCoG = solid.CenterOfMass
cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4
cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4
cog.z = cog.z + sCoG.z * solid.Volume / Units.Metre.Value**4
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
Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3
App.ActiveDocument.removeObject(common.Name)
App.ActiveDocument.removeObject(ship_shape.Name)
App.ActiveDocument.removeObject(box.Name)
App.ActiveDocument.recompute()
bbox = shape.BoundBox
Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin)
# Undo the transformations
# 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.rotateZ(-math.radians(yaw))
m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0))
m.rotateY(math.radians(trim))
m.move(Vector(0.0,
-draft * math.sin(math.radians(roll)),
base_z / Units.Metre.Value))
base_z))
m.rotateX(-math.radians(roll))
B.transform(m)
# Return the computed data
return [DENS*vol, Vector(B.X, B.Y, B.Z), 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
shape = shape.copy()
_draft = draft * Units.Metre.Value
shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim)
shape.translate(Vector(0.0, 0.0, -_draft))
bbox = shape.BoundBox
xmin = bbox.XMin
xmax = bbox.XMax
ymin = bbox.YMin
ymax = bbox.YMax
# Create the "sea" box
L = xmax - xmin
B = ymax - ymin
p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0)
try:
box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p)
except Part.OCCError:
return 0.0
for f in shape.Faces:
try:
common = box.common(f)
except Part.OCCError:
continue
area = area + common.Area
return area / Units.Metre.Value**2
cb = vol / Vol
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null volume found during the displacement"
" computation!",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
cb = 0.0
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.
# 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
angle = factor * math.degrees(math.atan2(
0.01,
0.5 * ship.Length.getValueAs('m').Value))
newTrim = trim + angle
data = displacement(ship, draft, 0.0, newTrim, 0.0)
mom0 = -disp * xcb
mom1 = -data[0] * data[1].x
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, 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
def floatingArea(ship, draft=None,
roll=Units.parseQuantity("0 deg"),
trim=Units.parseQuantity("0 deg")):
"""Compute the ship floating area
shape = ship.Shape.copy()
_draft = draft * Units.Metre.Value
shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim)
shape.translate(Vector(0.0, 0.0, -_draft))
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 wanna 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,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
area = Units.Quantity(0.0, Units.Area)
bbox = shape.BoundBox
xmin = bbox.XMin
xmax = bbox.XMax
ymin = bbox.YMin
ymax = bbox.YMax
# Create the "sea" box
L = xmax - xmin
B = ymax - ymin
p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0)
Area = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin)
try:
box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p)
except Part.OCCError:
return [area, cf]
cf = area.Value / Area
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null area found during the floating area"
" computation!",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
cf = 0.0
maxX = bbox.XMin / Units.Metre.Value
minX = bbox.XMax / Units.Metre.Value
maxY = bbox.YMin / Units.Metre.Value
minY = bbox.YMax / Units.Metre.Value
for s in shape.Solids:
try:
common = box.common(s)
except Part.OCCError:
continue
if common.Volume == 0.0:
continue
# Recompute the object adding it to the scene. OpenCASCADE must be
# performing an internal tesellation doing that
try:
Part.show(common)
except (TypeError,Part.OCCError):
continue
# Divide the solid by faces and filter the well placed ones
faces = common.Faces
for f in faces:
faceBounds = f.BoundBox
# Orientation filter
if faceBounds.ZMax - faceBounds.ZMin > 0.00001:
continue
# Position filter
if abs(faceBounds.ZMax) > 0.00001:
continue
area = area + f.Area / Units.Metre.Value**2
maxX = max(maxX, faceBounds.XMax / Units.Metre.Value)
minX = min(minX, faceBounds.XMin / Units.Metre.Value)
maxY = max(maxY, faceBounds.YMax / Units.Metre.Value)
minY = min(minY, faceBounds.YMin / Units.Metre.Value)
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]
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].
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 = 7.0
B0 = displacement(ship, draft, 0.0, trim, 0.0)[1]
maxRoll = Units.parseQuantity("7 deg")
BM = 0.0
for i in range(nRoll):
roll = (maxRoll / nRoll)*(i + 1)
B1 = displacement(ship, draft, roll, trim, 0.0)[1]
roll = (maxRoll / nRoll) * (i + 1)
_, B1, _ = displacement(ship, draft, roll, trim)
# * M
# / \
# / \ BM ==|> BM = (BB/2) / sin(alpha/2)
# / \
# *-------*
# BB
BB = [B1.y - B0.y, B1.z - B0.z]
BB = math.sqrt(BB[0] * BB[0] + BB[1] * BB[1])
# nRoll is acting as the weight function
BM = BM + 0.5 * BB / math.sin(math.radians(0.5 * roll)) / nRoll
return BM
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):
""" Calculate main frame coefficient.
@param ship Selected ship instance
@param draft Draft.
@return Main frame coefficient
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
"""
cm = 0.0
maxY = 0.0
minY = 0.0
if draft is None:
draft = ship.Draft
shape = ship.Shape.copy()
shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value))
x = 0.0
area = 0.0
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,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
area = 0.0
bbox = shape.BoundBox
xmin = bbox.XMin
xmax = bbox.XMax
ymin = bbox.YMin
ymax = bbox.YMax
Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin)
# Create the "sea" box
L = xmax - xmin
B = ymax - ymin
p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0)
try:
box = Part.makeBox(1.5 * L, 3.0 * B, - bbox.ZMin + 1.0, p)
except Part.OCCError:
return cm
cm = area / Area
except ZeroDivisionError:
msg = QtGui.QApplication.translate(
"ship_console",
"ZeroDivisionError: Null area found during the main frame area"
" coefficient computation!",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
cm = 0.0
maxY = bbox.YMin / Units.Metre.Value
minY = bbox.YMax / Units.Metre.Value
for s in shape.Solids:
try:
common = box.common(s)
except Part.OCCError:
continue
if common.Volume == 0.0:
continue
# Recompute the object adding it to the scene. OpenCASCADE must be
# performing an internal tesellation doing that
try:
Part.show(common)
except (TypeError,Part.OCCError):
continue
# Divide the solid by faces and filter the well placed ones
faces = common.Faces
for f in faces:
faceBounds = f.BoundBox
# Orientation filter
if faceBounds.XMax - faceBounds.XMin > 0.00001:
continue
# Position filter
if abs(faceBounds.XMax - x) > 0.00001:
continue
area = area + f.Area / Units.Metre.Value**2
maxY = max(maxY, faceBounds.YMax / Units.Metre.Value)
minY = min(minY, faceBounds.YMin / Units.Metre.Value)
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.
"""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):
""" 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.
"""Compute all the hydrostatics.
Position argument:
ship -- Ship instance
faces -- Ship external faces
draft -- Ship draft
trim -- Trim angle
"""
# Hydrostatics computation
dispData = displacement(ship, draft, 0.0, trim, 0.0)
disp, B, cb = displacement(ship, draft=draft, trim=trim)
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)
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 = dispData[0]
self.xcb = dispData[1].x
self.disp = disp
self.xcb = Units.Quantity(B.x, Units.Length)
self.wet = wet
self.farea = farea[0]
self.farea = farea
self.mom = mom
self.KBt = dispData[1].z
self.KBt = Units.Quantity(B.z, Units.Length)
self.BMt = bm
self.Cb = dispData[2]
self.Cf = farea[1]
self.Cm = cm
self.Cb = cb
self.Cf = cf
self.Cm = cm