Merge pull request #80 from sanguinariojoe/master

FreeCAD Ship
This commit is contained in:
wwmayer 2016-01-25 21:33:32 +01:00
commit 173d197fe0
50 changed files with 3082 additions and 662 deletions

View File

@ -29,6 +29,7 @@ SET(ShipCreateShip_SRCS
shipCreateShip/Preview.py
shipCreateShip/TaskPanel.py
shipCreateShip/TaskPanel.ui
shipCreateShip/Tools.ui
)
SOURCE_GROUP("shipcreateship" FILES ${ShipCreateShip_SRCS})
@ -62,6 +63,7 @@ SET(ShipCreateWeight_SRCS
shipCreateWeight/__init__.py
shipCreateWeight/TaskPanel.py
shipCreateWeight/TaskPanel.ui
shipCreateWeight/Tools.py
)
SOURCE_GROUP("shipcreateweight" FILES ${ShipCreateWeight_SRCS})
@ -69,6 +71,7 @@ SET(ShipCreateTank_SRCS
shipCreateTank/__init__.py
shipCreateTank/TaskPanel.py
shipCreateTank/TaskPanel.ui
shipCreateTank/Tools.py
)
SOURCE_GROUP("shipcreatetank" FILES ${ShipCreateTank_SRCS})
@ -77,9 +80,25 @@ SET(ShipCapacityCurve_SRCS
shipCapacityCurve/PlotAux.py
shipCapacityCurve/TaskPanel.py
shipCapacityCurve/TaskPanel.ui
shipCapacityCurve/Tools.py
)
SOURCE_GROUP("shipcapacitycurve" FILES ${ShipCapacityCurve_SRCS})
SET(ShipCreateLoadCondition_SRCS
shipCreateLoadCondition/__init__.py
shipCreateLoadCondition/Tools.py
)
SOURCE_GROUP("shipcreateloadcondition" FILES ${ShipCreateLoadCondition_SRCS})
SET(ShipGZ_SRCS
shipGZ/__init__.py
shipGZ/PlotAux.py
shipGZ/TaskPanel.py
shipGZ/TaskPanel.ui
shipGZ/Tools.py
)
SOURCE_GROUP("shipgz" FILES ${ShipGZ_SRCS})
SET(ShipUtils_SRCS
shipUtils/__init__.py
shipUtils/Locale.py
@ -89,7 +108,7 @@ SET(ShipUtils_SRCS
)
SOURCE_GROUP("shiputils" FILES ${ShipUtils_SRCS})
SET(all_files ${ShipMain_SRCS} ${ShipExamples_SRCS} ${ShipLoadExample_SRCS} ${ShipCreateShip_SRCS} ${ShipOutlineDraw_SRCS} ${ShipAreasCurve_SRCS} ${ShipHydrostatics_SRCS} ${ShipCreateWeight_SRCS} ${ShipCreateTank_SRCS} ${ShipCapacityCurve_SRCS} ${ShipUtils_SRCS})
SET(all_files ${ShipMain_SRCS} ${ShipExamples_SRCS} ${ShipLoadExample_SRCS} ${ShipCreateShip_SRCS} ${ShipOutlineDraw_SRCS} ${ShipAreasCurve_SRCS} ${ShipHydrostatics_SRCS} ${ShipCreateWeight_SRCS} ${ShipCreateTank_SRCS} ${ShipCapacityCurve_SRCS} ${ShipCreateLoadCondition_SRCS} ${ShipGZ_SRCS} ${ShipUtils_SRCS})
ADD_CUSTOM_TARGET(Ship ALL
SOURCES ${all_files} ${Ship_QRC_SRCS}
@ -156,6 +175,18 @@ INSTALL(
DESTINATION
Mod/Ship/shipCapacityCurve
)
INSTALL(
FILES
${ShipCreateLoadCondition_SRCS}
DESTINATION
Mod/Ship/shipCreateLoadCondition
)
INSTALL(
FILES
${ShipGZ_SRCS}
DESTINATION
Mod/Ship/shipGZ
)
INSTALL(
FILES
${ShipUtils_SRCS}

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -52,12 +52,10 @@ class ShipWorkbench(Workbench):
"Ship_Hydrostatics"]
weightslist = ["Ship_Weight",
"Ship_Tank",
"Ship_Capacity"]
"""
weightslist = ["Ship_Weights",
"Ship_CreateTank",
"Ship_Capacity",
"Ship_LoadCondition",
"Ship_GZ"]
"""
self.appendToolbar(
str(QtCore.QT_TRANSLATE_NOOP("Ship", "Ship design")),
shiplist)

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -24,8 +24,6 @@
import time
from math import *
from PySide import QtGui, QtCore
from pivy.coin import *
from pivy import coin
import FreeCAD
import FreeCADGui
from FreeCAD import Base, Vector
@ -109,6 +107,15 @@ class Ship:
"Tanks",
"Ship",
tooltip).Tanks = []
tooltip = str(QtGui.QApplication.translate(
"Ship",
"Set of load conditions",
None,
QtGui.QApplication.UnicodeUTF8))
obj.addProperty("App::PropertyStringList",
"LoadConditions",
"Ship",
tooltip).LoadConditions = []
obj.Proxy = self
@ -122,6 +129,91 @@ class Ship:
if prop == "Length" or prop == "Breadth" or prop == "Draft":
pass
def cleanWeights(self, fp):
"""Reanalyse the weights list looking for duplicated opbjects, or
removed ones.
"""
if not len(fp.Weights):
return
# Filter out the duplicated elements
filtered_list = []
[filtered_list.append(x) for x in fp.Weights if x not in filtered_list]
if cmp(fp.Weights, filtered_list):
fp.Weights = filtered_list
# Filter out the removed/non-valid objects
object_names = []
for obj in fp.Document.Objects:
object_names.append(obj.Name)
filtered_list = []
for obj_name in fp.Weights:
if obj_name in object_names:
for obj in fp.Document.Objects:
if obj.Name == obj_name:
try:
if obj.IsWeight: filtered_list.append(obj_name)
except:
pass
break
if cmp(fp.Weights, filtered_list):
fp.Weights = filtered_list
def cleanTanks(self, fp):
"""Reanalyse the weights list looking for duplicated opbjects, or
removed ones.
"""
if not len(fp.Tanks):
return
# Filter out the duplicated elements
filtered_list = []
[filtered_list.append(x) for x in fp.Tanks if x not in filtered_list]
if cmp(fp.Tanks, filtered_list):
fp.Tanks = filtered_list
# Filter out the removed/non-valid objects
object_names = []
for obj in fp.Document.Objects:
object_names.append(obj.Name)
filtered_list = []
for obj_name in fp.Tanks:
if obj_name in object_names:
for obj in fp.Document.Objects:
if obj.Name == obj_name:
try:
if obj.IsTank: filtered_list.append(obj_name)
except:
pass
break
if cmp(fp.Tanks, filtered_list):
fp.Tanks = filtered_list
def cleanLoadConditions(self, fp):
"""Reanalyse the weights list looking for duplicated opbjects, or
removed ones.
"""
if not len(fp.LoadConditions):
return
# Filter out the duplicated elements
filtered_list = []
[filtered_list.append(x) for x in fp.LoadConditions if x not in filtered_list]
if cmp(fp.LoadConditions, filtered_list):
fp.LoadConditions = filtered_list
# Filter out the removed/non-valid objects
object_names = []
for obj in fp.Document.Objects:
object_names.append(obj.Name)
filtered_list = []
for obj_name in fp.LoadConditions:
if obj_name in object_names:
for obj in fp.Document.Objects:
if obj.Name == obj_name:
try:
if obj.TypeId == 'Spreadsheet::Sheet':
filtered_list.append(obj_name)
except:
pass
break
if cmp(fp.LoadConditions, filtered_list):
fp.LoadConditions = filtered_list
def execute(self, fp):
"""Detects the entity recomputations.
@ -242,6 +334,16 @@ class ViewProviderShip:
del obj.Tanks[i - bad_linked]
bad_linked += 1
# Claim the loading conditions
bad_linked = 0
for i, t in enumerate(obj.LoadConditions):
try:
t_obj = FreeCAD.ActiveDocument.getObject(t)
objs.append(t_obj)
except:
del obj.LoadConditions[i - bad_linked]
bad_linked += 1
return objs
def getIcon(self):

39
src/Mod/Ship/Ship.py Normal file
View File

@ -0,0 +1,39 @@
#***************************************************************************
#* *
#* Copyright (c) 2015 *
#* 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 *
#* *
#***************************************************************************
__title__="FreeCAD Ship module"
__author__ = "Jose Luis Cercos-Pita"
__url__ = "http://www.freecadweb.org"
__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, displacement, wettedArea, moment
from shipHydrostatics.Tools import floatingArea, BMT, mainFrameCoeff
from shipCreateWeight.Tools import createWeight
from shipCreateTank.Tools import createTank
from shipCapacityCurve.Tools import tankCapacityCurve
from shipCreateLoadCondition.Tools import createLoadCondition
from shipGZ.Tools import gz

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -170,6 +170,40 @@ class TankCapacity:
'ToolTip': ToolTip}
class LoadCondition:
def Activated(self):
import shipCreateLoadCondition
shipCreateLoadCondition.load()
def GetResources(self):
MenuText = QtCore.QT_TRANSLATE_NOOP(
'ship_loadcondition',
'Create a new loading condition')
ToolTip = QtCore.QT_TRANSLATE_NOOP(
'ship_loadcondition',
'Create a new load condition spreadsheet')
return {'Pixmap': 'Ship_LoadCondition',
'MenuText': MenuText,
'ToolTip': ToolTip}
class GZ:
def Activated(self):
import shipGZ
shipGZ.load()
def GetResources(self):
MenuText = QtCore.QT_TRANSLATE_NOOP(
'ship_gz',
'GZ curve computation')
ToolTip = QtCore.QT_TRANSLATE_NOOP(
'ship_gz',
'Plot the GZ curve')
return {'Pixmap': 'Ship_GZ',
'MenuText': MenuText,
'ToolTip': ToolTip}
FreeCADGui.addCommand('Ship_LoadExample', LoadExample())
FreeCADGui.addCommand('Ship_CreateShip', CreateShip())
FreeCADGui.addCommand('Ship_OutlineDraw', OutlineDraw())
@ -178,3 +212,5 @@ FreeCADGui.addCommand('Ship_Hydrostatics', Hydrostatics())
FreeCADGui.addCommand('Ship_Weight', CreateWeight())
FreeCADGui.addCommand('Ship_Tank', CreateTank())
FreeCADGui.addCommand('Ship_Capacity', TankCapacity())
FreeCADGui.addCommand('Ship_LoadCondition', LoadCondition())
FreeCADGui.addCommand('Ship_GZ', GZ())

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -24,14 +24,16 @@
import time
from math import *
from PySide import QtGui, QtCore
from pivy.coin import *
from pivy import coin
import FreeCAD
import FreeCADGui
from FreeCAD import Base, Vector
import FreeCAD as App
import FreeCADGui as Gui
from FreeCAD import Base, Vector, Matrix, Placement, Rotation
import Part
import Units
from shipUtils import Paths, Math
import shipUtils.Units as USys
COMMON_BOOLEAN_ITERATIONS = 10
class Tank:
@ -54,28 +56,6 @@ class Tank:
"IsTank",
"Tank",
tooltip).IsTank = True
# Add the volume property (The volume of fluid will be set by each
# loading condition)
tooltip = str(QtGui.QApplication.translate(
"ship_tank",
"Volume of fluid [m^3]",
None,
QtGui.QApplication.UnicodeUTF8))
obj.addProperty("App::PropertyFloat",
"Vol",
"Tank",
tooltip).Vol = 0.0
# Add the density property (The volume of fluid will be set by each
# loading condition)
tooltip = str(QtGui.QApplication.translate(
"ship_tank",
"Density [kg / m^3]",
None,
QtGui.QApplication.UnicodeUTF8))
obj.addProperty("App::PropertyFloat",
"Dens",
"Tank",
tooltip).Dens = 0.0
# Set the subshapes
obj.Shape = Part.makeCompound(shapes)
@ -99,52 +79,154 @@ class Tank:
"""
pass
def setFillingLevel(self, fp, level):
"""Compute the mass of the object, already taking into account the
type of subentities.
def getVolume(self, fp, level, return_shape=False):
"""Return the fluid volume inside the tank, provided the filling level.
Keyword arguments:
fp -- Part::FeaturePython object affected.
level -- Percentage of filling level (from 0 to 100).
level -- Percentage of filling level (interval [0, 1]).
return_shape -- False if the tool should return the fluid volume value,
True if the tool should return the volume shape.
"""
shape = fp.Shape
solids = shape.Solids
if level <= 0.0:
if return_shape:
return Part.Vertex()
return Units.Quantity(0.0, Units.Volume)
if level >= 1.0:
if return_shape:
return fp.Shape.copy()
return Units.Quantity(fp.Shape.Volume, Units.Volume)
# Get the cutting box
bbox = shape.BoundBox
z_min = bbox.ZMin
z_max = bbox.ZMax
# Build up the cutting box
bbox = fp.Shape.BoundBox
dx = bbox.XMax - bbox.XMin
dy = bbox.YMax - bbox.YMin
dz = level / 100.0 * (z_max - z_min)
z = z_min + dz
try:
box = Part.makeBox(3.0 * dx,
3.0 * dy,
(z_max - z_min) + dz,
Vector(bbox.XMin - dx,
bbox.YMin - dy,
bbox.ZMin - (z_max - z_min)))
except:
fp.Vol = 0.0
return Units.parseQuantity('0 m^3')
dz = bbox.ZMax - bbox.ZMin
# Start computing the common part of each solid component with the
# cutting box, adding the volume
box = App.ActiveDocument.addObject("Part::Box","Box")
length_format = USys.getLengthFormat()
box.Placement = Placement(Vector(bbox.XMin - dx,
bbox.YMin - dy,
bbox.ZMin - dz),
Rotation(App.Vector(0,0,1),0))
box.Length = length_format.format(3.0 * dx)
box.Width = length_format.format(3.0 * dy)
box.Height = length_format.format((1.0 + level) * dz)
# Create a new object on top of a copy of the tank shape
Part.show(fp.Shape.copy())
tank = App.ActiveDocument.Objects[-1]
# Compute the common boolean operation
App.ActiveDocument.recompute()
common = App.activeDocument().addObject("Part::MultiCommon",
"TankVolHelper")
common.Shapes = [tank, 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",
"Tank volume operation failed. The tool is retrying that"
" slightly moving the free surface position",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintWarning(msg + '\n')
rand_bounds = 0.01 * dz
i = 0
while len(common.Shape.Solids) == 0 and i < COMMON_BOOLEAN_ITERATIONS:
i += 1
box.Height = length_format.format(
(1.0 + level) * dz + random.uniform(-random_bounds,
random_bounds))
App.ActiveDocument.recompute()
if return_shape:
ret_value = common.Shape.copy()
else:
ret_value = Units.Quantity(common.Shape.Volume, Units.Volume)
App.ActiveDocument.removeObject(common.Name)
App.ActiveDocument.removeObject(tank.Name)
App.ActiveDocument.removeObject(box.Name)
App.ActiveDocument.recompute()
return ret_value
def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"),
trim=Units.parseQuantity("0 deg")):
"""Return the fluid volume center of gravity, provided the volume of
fluid inside the tank.
The returned center of gravity is refered to the untransformed ship.
Keyword arguments:
fp -- Part::FeaturePython object affected.
vol -- Volume of fluid.
roll -- Ship roll angle.
trim -- Ship trim angle.
If the fluid volume is bigger than the total tank one, it will be
conveniently clamped.
"""
# Change the units of the volume, and clamp the value
if vol <= 0.0:
return Vector()
if vol >= fp.Shape.Volume:
vol = 0.0
for solid in fp.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
return cog
# Get a first estimation of the level
level = vol.Value / fp.Shape.Volume
# Transform the tank shape
current_placement = fp.Placement
m = current_placement.toMatrix()
m.rotateX(roll.getValueAs("rad"))
m.rotateY(-trim.getValueAs("rad"))
fp.Placement = Placement(m)
# Iterate to find the fluid shape
for i in range(COMMON_BOOLEAN_ITERATIONS):
shape = self.getVolume(fp, level, return_shape=True)
error = (vol.Value - shape.Volume) / fp.Shape.Volume
if abs(error) < 0.01:
break
level += error
# Get the center of gravity
vol = 0.0
for s in solids:
try:
fluid = s.common(box)
v = fluid.Volume
except:
v = 0.0
vol += v
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
# Get the volume quantity and store it with the right units
vol = Units.Quantity(vol, Units.Volume)
fp.Vol = vol.getValueAs("m^3").Value
# Untransform the object to retrieve the original position
fp.Placement = current_placement
p = Part.Point(cog)
m = Matrix()
m.rotateY(trim.getValueAs("rad"))
m.rotateX(-roll.getValueAs("rad"))
p.rotate(Placement(m))
return vol
return Vector(p.X, p.Y, p.Z)
class ViewProviderTank:
@ -184,7 +266,7 @@ class ViewProviderTank:
def getDefaultDisplayMode(self):
"""Return the name of the default display mode. It must be defined in
getDisplayModes."""
return "Shaded"
return "Flat Lines"
def setDisplayMode(self, mode):
"""Map the display mode defined in attach with those defined in

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -24,8 +24,6 @@
import time
from math import *
from PySide import QtGui, QtCore
from pivy.coin import *
from pivy import coin
import FreeCAD
import FreeCADGui
from FreeCAD import Base, Vector
@ -38,7 +36,7 @@ class Weight:
def __init__(self, obj, shapes, ship):
""" Transform a generic object to a ship instance.
Keyword arguments:
Position arguments:
obj -- Part::FeaturePython created object which should be transformed
in a weight instance.
shapes -- Set of shapes which will compound the weight element.
@ -77,7 +75,7 @@ class Weight:
# Add the area density property for surface elements
tooltip = str(QtGui.QApplication.translate(
"ship_weight",
"Area density [kg / m^3]",
"Area density [kg / m^2]",
None,
QtGui.QApplication.UnicodeUTF8))
obj.addProperty("App::PropertyFloat",
@ -102,7 +100,7 @@ class Weight:
def onChanged(self, fp, prop):
"""Detects the ship data changes.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
prop -- Modified property name.
"""
@ -112,7 +110,7 @@ class Weight:
def execute(self, fp):
"""Detects the entity recomputations.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
"""
pass
@ -120,7 +118,7 @@ class Weight:
def _getPuntualMass(self, fp, shape):
"""Compute the mass of a puntual element.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Vertex shape object.
"""
@ -129,7 +127,7 @@ class Weight:
def _getLinearMass(self, fp, shape):
"""Compute the mass of a linear element.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Edge shape object.
"""
@ -140,7 +138,7 @@ class Weight:
def _getAreaMass(self, fp, shape):
"""Compute the mass of an area element.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Face shape object.
"""
@ -151,7 +149,7 @@ class Weight:
def _getVolumetricMass(self, fp, shape):
"""Compute the mass of a volumetric element.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Solid shape object.
"""
@ -163,24 +161,27 @@ class Weight:
"""Compute the mass of the object, already taking into account the
type of subentities.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
Returned value:
Object mass
"""
m = Units.parseQuantity('0 kg')
for s in fp.Shape.Solids:
m = m + self._getVolumetricMass(fp, s)
m += self._getVolumetricMass(fp, s)
for f in fp.Shape.Faces:
m = m + self._getAreaMass(fp, f)
m += self._getAreaMass(fp, f)
for e in fp.Shape.Edges:
m = m + self._getLinearMass(fp, e)
m += self._getLinearMass(fp, e)
for v in fp.Shape.Vertexes:
m = m + self._getPuntualMass(fp, v)
m += self._getPuntualMass(fp, v)
return m
def _getPuntualMoment(self, fp, shape):
"""Compute the moment of a puntual element (respect to 0, 0, 0).
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Vertex shape object.
"""
@ -193,7 +194,7 @@ class Weight:
def _getLinearMoment(self, fp, shape):
"""Compute the mass of a linear element (respect to 0, 0, 0).
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Edge shape object.
"""
@ -207,7 +208,7 @@ class Weight:
def _getAreaMoment(self, fp, shape):
"""Compute the mass of an area element (respect to 0, 0, 0).
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Face shape object.
"""
@ -221,7 +222,7 @@ class Weight:
def _getVolumetricMoment(self, fp, shape):
"""Compute the mass of a volumetric element (respect to 0, 0, 0).
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
shape -- Solid shape object.
"""
@ -236,8 +237,11 @@ class Weight:
"""Compute the mass of the object, already taking into account the
type of subentities.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
Returned value:
List of moments toward x, y and z
"""
m = [Units.parseQuantity('0 kg*m'),
Units.parseQuantity('0 kg*m'),
@ -264,15 +268,18 @@ class Weight:
"""Compute the mass of the object, already taking into account the
type of subentities.
Keyword arguments:
Position arguments:
fp -- Part::FeaturePython object affected.
Returned value:
Center of Mass vector
"""
mass = self.getMass(fp)
moment = self.getMoment(fp)
cog = []
for i in range(len(moment)):
cog.append(moment[i] / mass)
return cog
return Vector(cog[0].Value, cog[1].Value, cog[2].Value)
class ViewProviderWeight:
@ -312,7 +319,7 @@ class ViewProviderWeight:
def getDefaultDisplayMode(self):
"""Return the name of the default display mode. It must be defined in
getDisplayModes."""
return "Shaded"
return "Flat Lines"
def setDisplayMode(self, mode):
"""Map the display mode defined in attach with those defined in

View File

@ -6,6 +6,7 @@
<file>icons/Ship_GZ.svg</file>
<file>icons/Ship_Hydrostatics.svg</file>
<file>icons/Ship_Load.svg</file>
<file>icons/Ship_LoadCondition.svg</file>
<file>icons/Ship_Logo.svg</file>
<file>icons/Ship_Module.svg</file>
<file>icons/Ship_OutlineDraw.svg</file>

View File

@ -0,0 +1,660 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64px"
height="64px"
id="svg2985"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="Ship_LoadCondition.svg">
<defs
id="defs2987">
<linearGradient
id="linearGradient3979">
<stop
id="stop3981"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3983"
offset="1"
style="stop-color:#ff9600;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3971">
<stop
id="stop3973"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3975"
offset="1"
style="stop-color:#be7300;stop-opacity:1;" />
</linearGradient>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Send"
style="overflow:visible;">
<path
id="path4031"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
transform="scale(0.2) rotate(180) translate(6,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Send"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Send"
style="overflow:visible;">
<path
id="path4049"
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.3) rotate(180) translate(-2.3,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Sstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Sstart"
style="overflow:visible">
<path
id="path4046"
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.3) translate(-2.3,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Mstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Mstart"
style="overflow:visible">
<path
id="path4040"
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) translate(0,0)" />
</marker>
<linearGradient
id="linearGradient3900">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3902" />
<stop
style="stop-color:#a0a0a0;stop-opacity:1;"
offset="1"
id="stop3904" />
</linearGradient>
<linearGradient
id="linearGradient3882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3884" />
<stop
style="stop-color:#960000;stop-opacity:1;"
offset="1"
id="stop3886" />
</linearGradient>
<linearGradient
id="linearGradient3860">
<stop
style="stop-color:#1e76e3;stop-opacity:1;"
offset="0"
id="stop3862" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3864" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3860-6"
id="linearGradient3866-5"
x1="31.125395"
y1="61.410763"
x2="30.113636"
y2="12.160761"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3860-6">
<stop
style="stop-color:#5a9ff5;stop-opacity:1;"
offset="0"
id="stop3862-4" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3864-4" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3905"
id="linearGradient3949"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3905">
<stop
style="stop-color:#bebebe;stop-opacity:1;"
offset="0"
id="stop3907" />
<stop
style="stop-color:#585858;stop-opacity:1;"
offset="1"
id="stop3909" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3897"
id="linearGradient3951"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3897">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3899" />
<stop
style="stop-color:#9f9f9f;stop-opacity:1;"
offset="1"
id="stop3901" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3905"
id="linearGradient3941"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3176">
<stop
style="stop-color:#bebebe;stop-opacity:1;"
offset="0"
id="stop3178" />
<stop
style="stop-color:#585858;stop-opacity:1;"
offset="1"
id="stop3180" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3897"
id="linearGradient3943"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3183">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3185" />
<stop
style="stop-color:#9f9f9f;stop-opacity:1;"
offset="1"
id="stop3187" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3905"
id="linearGradient3945"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3190">
<stop
style="stop-color:#bebebe;stop-opacity:1;"
offset="0"
id="stop3192" />
<stop
style="stop-color:#585858;stop-opacity:1;"
offset="1"
id="stop3194" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3897"
id="linearGradient3947"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="7.7183714"
x2="62.196697"
y2="7.7183714" />
<linearGradient
id="linearGradient3197">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3199" />
<stop
style="stop-color:#9f9f9f;stop-opacity:1;"
offset="1"
id="stop3201" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3925"
id="linearGradient3961"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3925">
<stop
style="stop-color:#c98b8b;stop-opacity:1;"
offset="0"
id="stop3927" />
<stop
style="stop-color:#800000;stop-opacity:1;"
offset="1"
id="stop3929" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3917"
id="linearGradient3963"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3917">
<stop
style="stop-color:#450000;stop-opacity:1;"
offset="0"
id="stop3919" />
<stop
style="stop-color:#c77c7c;stop-opacity:1;"
offset="1"
id="stop3921" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3925"
id="linearGradient3953"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3212">
<stop
style="stop-color:#c98b8b;stop-opacity:1;"
offset="0"
id="stop3214" />
<stop
style="stop-color:#800000;stop-opacity:1;"
offset="1"
id="stop3216" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3917"
id="linearGradient3955"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3219">
<stop
style="stop-color:#450000;stop-opacity:1;"
offset="0"
id="stop3221" />
<stop
style="stop-color:#c77c7c;stop-opacity:1;"
offset="1"
id="stop3223" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3925"
id="linearGradient3957"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3226">
<stop
style="stop-color:#c98b8b;stop-opacity:1;"
offset="0"
id="stop3228" />
<stop
style="stop-color:#800000;stop-opacity:1;"
offset="1"
id="stop3230" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3917"
id="linearGradient3959"
gradientUnits="userSpaceOnUse"
x1="4.8033009"
y1="35.450565"
x2="62.196697"
y2="35.450565" />
<linearGradient
id="linearGradient3233">
<stop
style="stop-color:#450000;stop-opacity:1;"
offset="0"
id="stop3235" />
<stop
style="stop-color:#c77c7c;stop-opacity:1;"
offset="1"
id="stop3237" />
</linearGradient>
<linearGradient
y2="35.450565"
x2="62.196697"
y1="35.450565"
x1="4.8033009"
gradientUnits="userSpaceOnUse"
id="linearGradient3246"
xlink:href="#linearGradient3925"
inkscape:collect="always" />
<linearGradient
y2="35.450565"
x2="62.196697"
y1="35.450565"
x1="4.8033009"
gradientUnits="userSpaceOnUse"
id="linearGradient3248"
xlink:href="#linearGradient3917"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="105.96534"
inkscape:cy="22.492481"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1366"
inkscape:window-height="720"
inkscape:window-x="-2"
inkscape:window-y="-3"
inkscape:window-maximized="1" />
<metadata
id="metadata2990">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g3933"
transform="matrix(0.95688842,0,0,0.82320192,0.252675,8.8113396)">
<g
style="fill:url(#linearGradient3949);fill-opacity:1;stroke:url(#linearGradient3951);stroke-opacity:1"
id="g3893">
<path
sodipodi:nodetypes="cccscc"
inkscape:connector-curvature="0"
id="path3883"
d="M 33.5,-3.838753 C 29.699302,1.1109944 5.3033008,8.415215 5.3033008,8.415215 l 0,10.860281 c 0,0 8.6620582,-7.954951 24.2184072,-7.954951 15.556349,0 3.978292,0 3.978292,0 0,0 0,-9.2346409 0,-15.159298"
style="fill:url(#linearGradient3941);fill-opacity:1;stroke:url(#linearGradient3943);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
<path
style="fill:url(#linearGradient3945);fill-opacity:1;stroke:url(#linearGradient3947);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="M 33.5,-3.838753 C 37.300698,1.1109944 61.696699,8.415215 61.696699,8.415215 l 0,10.860281 c 0,0 -8.662058,-7.954951 -24.218407,-7.954951 -15.556349,0 -3.978292,0 -3.978292,0 0,0 0,-9.2346409 0,-15.159298"
id="path3891-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscc" />
</g>
<g
style="fill:url(#linearGradient3246);fill-opacity:1;stroke:url(#linearGradient3248)"
id="g3913">
<path
style="fill:url(#linearGradient3953);fill-opacity:1;stroke:url(#linearGradient3955);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 33.5,59.580583 c -12.727921,0 -28.1966992,-6.540738 -28.1966992,-24.571961 0,-10.076271 0,-15.733126 0,-15.733126 0,0 8.6620582,-7.954951 24.2184072,-7.954951 15.556349,0 3.978292,0 3.978292,0 z"
id="path3064"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cscscc" />
<path
sodipodi:nodetypes="cscscc"
inkscape:connector-curvature="0"
id="path3889-6"
d="m 33.5,59.580583 c 12.727921,0 28.196699,-6.540738 28.196699,-24.571961 0,-10.076271 0,-15.733126 0,-15.733126 0,0 -8.662058,-7.954951 -24.218407,-7.954951 -15.556349,0 -3.978292,0 -3.978292,0 z"
style="fill:url(#linearGradient3957);fill-opacity:1;stroke:url(#linearGradient3959);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
</g>
</g>
<g
id="g4101"
transform="matrix(1.5399613,0,0,1.5399613,-33.936572,-30.866243)">
<g
style="stroke-width:1.7280972;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(0.86800673,0,0,0.86800673,10.345105,8.1458701)"
id="g4047">
<g
style="stroke-width:1.7280972;stroke-miterlimit:4;stroke-dasharray:none"
id="g4043">
<path
inkscape:connector-curvature="0"
id="path3069"
d="m 15.75,42.75 22.25,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
transform="translate(10.75,12)"
d="m 23.75,30.75 c 0,4.004064 -3.245936,7.25 -7.25,7.25 -4.004064,0 -7.25,-3.245936 -7.25,-7.25 0,-4.004064 3.245936,-7.25 7.25,-7.25 4.004064,0 7.25,3.245936 7.25,7.25 z"
sodipodi:ry="7.25"
sodipodi:rx="7.25"
sodipodi:cy="30.75"
sodipodi:cx="16.5"
id="path3071"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:3.6"
sodipodi:type="arc" />
</g>
<g
style="stroke-width:1.7280972;stroke-miterlimit:4;stroke-dasharray:none"
id="g4031">
<path
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 39.75,38.75 9.75,0 m 0,3.125 10.125,0"
id="path3887"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
inkscape:connector-curvature="0"
id="path3889"
d="m 39.75,38.75 9.75,0 m 0,3.125 10.125,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3891"
d="m 49.5,47.875 10.125,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 49.5,47.875 10.125,0"
id="path3893"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 49.5,53.875 10.125,0"
id="path3895"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
id="path3897"
d="m 49.5,53.875 10.125,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3899"
d="m 49.5,59.875 10.125,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<g
style="stroke-width:1.7280972;stroke-miterlimit:4;stroke-dasharray:none"
transform="translate(10,12)"
id="g3907">
<g
style="stroke-width:1.7280972;stroke-miterlimit:4;stroke-dasharray:none"
id="g3903">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path3865"
d="m 29.75,20.75 9.75,0 0,27.125 10.125,0"
style="fill:none;stroke:#000000;stroke-width:1.7280972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
</g>
</g>
</g>
<g
id="g4087">
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
x="46.072853"
y="35.064079"
id="text4063"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4065"
x="46.072853"
y="35.064079"
style="font-size:3px">TF</tspan></text>
<text
sodipodi:linespacing="125%"
id="text4067"
y="40.501579"
x="45.135353"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
xml:space="preserve"><tspan
style="font-size:3px"
y="40.501579"
x="45.135353"
id="tspan4069"
sodipodi:role="line">F</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
x="61.31213"
y="43.054005"
id="text4071"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4073"
x="61.31213"
y="43.054005"
style="font-size:3px">T</tspan></text>
<text
sodipodi:linespacing="125%"
id="text4075"
y="48.258511"
x="61.31213"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
xml:space="preserve"><tspan
style="font-size:3px"
y="48.258511"
x="61.31213"
id="tspan4077"
sodipodi:role="line">S</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
x="61.31213"
y="53.46302"
id="text4079"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4081"
x="61.31213"
y="53.46302"
style="font-size:3px">W</tspan></text>
<text
sodipodi:linespacing="125%"
id="text4083"
y="58.744652"
x="62.211544"
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Sans"
xml:space="preserve"><tspan
style="font-size:3px;text-align:end;text-anchor:end"
y="58.744652"
x="62.211544"
id="tspan4085"
sodipodi:role="line">WNA</tspan></text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -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

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -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

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -50,25 +50,25 @@ class TaskPanel:
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.draft = self.widget(QtGui.QLineEdit, "Draft")
form.trim = self.widget(QtGui.QLineEdit, "Trim")
draft = Units.Quantity(Locale.fromString(
form.draft.text())).getValueAs('m').Value
trim = Units.Quantity(Locale.fromString(
form.trim.text())).getValueAs('deg').Value
data = Hydrostatics.displacement(self.ship,
draft,
0.0,
trim)
disp = data[0]
xcb = data[1].x
form.num = self.widget(QtGui.QSpinBox, "Num")
draft = Units.parseQuantity(Locale.fromString(form.draft.text()))
trim = Units.parseQuantity(Locale.fromString(form.trim.text()))
num = form.num.value()
disp, B, _ = Hydrostatics.displacement(self.ship,
draft,
Units.parseQuantity("0 deg"),
trim)
xcb = Units.Quantity(B.x, Units.Length)
data = Hydrostatics.areas(self.ship,
draft,
0.0,
trim)
num,
draft=draft,
trim=trim)
x = []
y = []
for i in range(0, len(data)):
x.append(data[i][0])
y.append(data[i][1])
x.append(data[i][0].getValueAs("m").Value)
y.append(data[i][1].getValueAs("m^2").Value)
PlotAux.Plot(x, y, disp, xcb, self.ship)
self.preview.clean()
return True
@ -104,6 +104,7 @@ class TaskPanel:
form.draft = self.widget(QtGui.QLineEdit, "Draft")
form.trim = self.widget(QtGui.QLineEdit, "Trim")
form.num = self.widget(QtGui.QSpinBox, "Num")
form.output = self.widget(QtGui.QTextEdit, "OutputData")
form.doc = QtGui.QTextDocument(form.output)
self.form = form
@ -183,6 +184,7 @@ class TaskPanel:
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.draft = self.widget(QtGui.QLineEdit, "Draft")
form.trim = self.widget(QtGui.QLineEdit, "Trim")
form.num = self.widget(QtGui.QSpinBox, "Num")
form.draft.setText(Locale.toString(length_format.format(
self.ship.Draft.getValueAs(USys.getLengthUnits()).Value)))
form.trim.setText(Locale.toString(angle_format.format(0.0)))
@ -202,6 +204,11 @@ class TaskPanel:
USys.getAngleUnits()).Value)))
except ValueError:
pass
try:
props.index("AreaCurveNum")
form.num.setValue(self.ship.AreaCurveNum)
except ValueError:
pass
# Update GUI
draft = Units.Quantity(form.draft.text()).getValueAs('m').Value
trim = Units.Quantity(form.trim.text()).getValueAs('deg').Value
@ -227,28 +234,21 @@ class TaskPanel:
self.widget(QtGui.QLabel, "TrimLabel").setText(
QtGui.QApplication.translate(
"ship_areas",
"Trim",
"Trim angle",
None,
QtGui.QApplication.UnicodeUTF8))
self.widget(QtGui.QLabel, "NumLabel").setText(
QtGui.QApplication.translate(
"ship_areas",
"Number of points",
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):
@ -265,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)
@ -305,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):
@ -347,11 +338,11 @@ class TaskPanel:
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.draft = self.widget(QtGui.QLineEdit, "Draft")
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
try:
@ -369,7 +360,7 @@ class TaskPanel:
"AreaCurveDraft",
"Ship",
tooltip)
self.ship.AreaCurveDraft = '{} m'.format(draft)
self.ship.AreaCurveDraft = draft
try:
props.index("AreaCurveTrim")
except ValueError:
@ -385,7 +376,23 @@ class TaskPanel:
"AreaCurveTrim",
"Ship",
tooltip)
self.ship.AreaCurveTrim = '{} deg'.format(trim)
self.ship.AreaCurveTrim = trim
try:
props.index("AreaCurveNum")
except ValueError:
try:
tooltip = str(QtGui.QApplication.translate(
"ship_areas",
"Areas curve tool number of points",
None,
QtGui.QApplication.UnicodeUTF8))
except:
tooltip = "Areas curve tool number of points"
self.ship.addProperty("App::PropertyInteger",
"AreaCurveNum",
"Ship",
tooltip)
self.ship.AreaCurveNum = num
def createTask():
@ -394,4 +401,4 @@ def createTask():
if panel.setupUi():
Gui.Control.closeDialog(panel)
return None
return panel
return panel

View File

@ -45,7 +45,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Trim</string>
<string>Trim angle</string>
</property>
</widget>
</item>
@ -55,6 +55,26 @@
<item row="1" column="1">
<widget class="Gui::InputField" name="Trim"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="NumLabel">
<property name="text">
<string>Number of points</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="Num">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -27,6 +27,8 @@ import FreeCAD
import FreeCADGui
from FreeCAD import Base
import Spreadsheet
import matplotlib.ticker as mtick
class Plot(object):
def __init__(self, l, z, v, tank):
@ -64,12 +66,17 @@ class Plot(object):
vols.line.set_linestyle('-')
vols.line.set_linewidth(2.0)
vols.line.set_color((0.0, 0.0, 0.0))
Plot.xlabel(r'Percentage of filling level')
Plot.xlabel(r'$\mathrm{level}$')
Plot.ylabel(r'$V \; [\mathrm{m}^3]$')
plt.axes.xaxis.label.set_fontsize(20)
plt.axes.yaxis.label.set_fontsize(20)
Plot.grid(True)
# Special percentage formatter for the x axis
fmt = '%.0f%%'
xticks = mtick.FormatStrFormatter(fmt)
plt.axes.xaxis.set_major_formatter(xticks)
# Now duplicate the axes
ax = Plot.addNewAxes()
# Y axis can be placed at right
@ -80,20 +87,22 @@ class Plot(object):
ax.yaxis.set_label_position('right')
# And X axis can be placed at top
ax.xaxis.tick_top()
ax.spines['top'].set_color((0.0, 0.0, 0.0))
ax.spines['top'].set_color((0.0, 0.0, 1.0))
ax.spines['bottom'].set_color('none')
ax.xaxis.set_ticks_position('top')
ax.xaxis.set_label_position('top')
# Plot the volume as a function of the level z coordinate
vols = Plot.plot(z, v, 'Capacity')
vols = Plot.plot(z, v, 'level')
vols.line.set_linestyle('-')
vols.line.set_linewidth(2.0)
vols.line.set_color((0.0, 0.0, 0.0))
vols.line.set_color((0.0, 0.0, 1.0))
Plot.xlabel(r'$z \; [\mathrm{m}]$')
Plot.ylabel(r'$V \; [\mathrm{m}^3]$')
ax.xaxis.label.set_fontsize(20)
ax.yaxis.label.set_fontsize(20)
ax.xaxis.label.set_color((0.0, 0.0, 1.0))
ax.tick_params(axis='x', colors=(0.0, 0.0, 1.0))
Plot.grid(True)
# End

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -26,6 +26,7 @@ import FreeCAD as App
import FreeCADGui as Gui
import Units
from PySide import QtGui, QtCore
import Tools
import PlotAux
import TankInstance as Instance
from shipUtils import Paths
@ -40,8 +41,21 @@ class TaskPanel:
def accept(self):
if self.tank is None:
return False
# Plot data
l, z, v = self.compute()
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.points = self.widget(QtGui.QSpinBox, "Points")
n = form.points.value()
points = Tools.tankCapacityCurve(self.tank, n)
l = []
z = []
v = []
for p in points:
l.append(p[0] * 100)
z.append(p[1].getValueAs("m").Value)
v.append(p[2].getValueAs("m^3").Value)
PlotAux.Plot(l, z, v, self.tank)
return True
@ -154,29 +168,6 @@ class TaskPanel:
None,
QtGui.QApplication.UnicodeUTF8))
def compute(self):
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.points = self.widget(QtGui.QSpinBox, "Points")
bbox = self.tank.Shape.BoundBox
dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length)
n = form.points.value()
dlevel = 100.0 / (n - 1)
l = [0.0]
v = [0.0]
z = [0.0]
for i in range(1, n):
level = i * dlevel
vol = self.tank.Proxy.setFillingLevel(self.tank, level)
l.append(level)
z.append(level / 100.0 * dz.getValueAs("m").Value)
v.append(vol.getValueAs("m^3").Value)
return (l, z, v)
def createTask():
panel = TaskPanel()
Gui.Control.showDialog(panel)

View File

@ -0,0 +1,60 @@
#***************************************************************************
#* *
#* Copyright (c) 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 FreeCAD as App
import Units
import WeightInstance as Instance
import shipUtils.Units as USys
from PySide import QtGui
def tankCapacityCurve(tank, n):
"""Create a tank capacity curve
Position arguments:
tank -- Tank object (see createTank)
ship -- n Number of filling levels to test
Returned value:
List of computed points. Each point contains the filling level percentage
(interval [0, 1]), the the filling level (0 for the bottom of the tank), and
the volume.
"""
bbox = tank.Shape.BoundBox
dz = Units.Quantity(bbox.ZMax - bbox.ZMin, Units.Length)
dlevel = 1.0 / (n - 1)
out = [(0.0, Units.parseQuantity("0 m"), Units.parseQuantity("0 m^3"))]
msg = QtGui.QApplication.translate(
"ship_console",
"Computing capacity curves",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintMessage(msg + '...\n')
for i in range(1, n):
App.Console.PrintMessage("\t{} / {}\n".format(i + 1, n))
level = i * dlevel
vol = tank.Proxy.getVolume(tank, level)
out.append((level, level * dz, level * vol))
return out

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -0,0 +1,124 @@
#***************************************************************************
#* *
#* Copyright (c) 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 detaillc. *
#* *
#* 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 FreeCAD as App
import Spreadsheet
import Units
READ_ONLY_FOREGROUND = (0.5, 0.5, 0.5)
READ_ONLY_BACKGROUND = (0.9, 0.9, 0.9)
def createLoadCondition(ship):
"""Create a new loading condition spreadsheet
Position arguments:
ship -- Ship object
Returned value:
lc -- The new loading condition spreadsheet object
The new spreadsheet will initialize all the tanks as empty. To modify the
fluid density and the filling level percentages the columns D and E should
be edited (the first tank can be found at the row 6)
For instance, the following code snippet can be used to set the first tank
50% filled with water:
lc.set("D6", "998.0")
lc.set("E6", "0.5")
The label of the tank can be extracted from the column C, for instance:
lc.get("C6")
Such information can be used to get the tank object (the followinbg code
may fail if either the tank has been removed, or several objects with the
same label already exist):
tank = App.ActiveDocument.getObjectsByLabel(lc.get("C6"))[0]
The tool will claim the new spreadsheet loading condition as a child of the
ship object. Please do not remove the partner ship object before removing
this new loading condition before.
"""
# Create the spreadsheet
lc = App.activeDocument().addObject('Spreadsheet::Sheet',
'LoadCondition')
# Add a description
lc.setForeground('A1:B2', READ_ONLY_FOREGROUND)
lc.setBackground('A1:B2', READ_ONLY_BACKGROUND)
lc.setAlignment('B1:B2', 'center', 'keep')
lc.setStyle('B1:B2', 'italic', 'add')
lc.set("A1", "Ship:")
lc.set("A2", "Load condition:")
lc.set("B1", "=" + ship.Name + ".Label")
lc.set("B2", "=Label")
# Add the weights data
lc.setAlignment('A4:A5', 'center', 'keep')
lc.setStyle('A4:A5', 'bold', 'add')
lc.setStyle('A4:A5', 'underline', 'add')
lc.set("A4", "WEIGHTS DATA")
lc.set("A5", "name")
for i in range(len(ship.Weights)):
weight = App.activeDocument().getObject(ship.Weights[i])
lc.set("A{}".format(i + 6), "=" + weight.Name + ".Label")
lc.setForeground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_FOREGROUND)
lc.setBackground('A4:A{}'.format(5 + len(ship.Weights)), READ_ONLY_BACKGROUND)
# Add the tanks data
lc.mergeCells('C4:E4')
lc.setForeground('C4:E5', READ_ONLY_FOREGROUND)
lc.setBackground('C4:E5', READ_ONLY_BACKGROUND)
lc.setAlignment('C4:E5', 'center', 'keep')
lc.setStyle('C4:E5', 'bold', 'add')
lc.setStyle('C4:E5', 'underline', 'add')
lc.set("C4", "TANKS DATA")
lc.set("C5", "name")
lc.set("D5", "Fluid density [kg/m^3]")
lc.set("E5", "Filling ratio (interval [0, 1])")
if len(ship.Tanks):
for i in range(len(ship.Tanks)):
tank = App.activeDocument().getObject(ship.Tanks[i])
lc.set("C{}".format(i + 6), "=" + tank.Name + ".Label")
lc.set("D{}".format(i + 6), "998.0")
lc.set("E{}".format(i + 6), "0.0")
lc.setForeground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_FOREGROUND)
lc.setBackground('C6:C{}'.format(5 + len(ship.Tanks)), READ_ONLY_BACKGROUND)
lc.setColumnWidth('A', 128)
lc.setColumnWidth('B', 128)
lc.setColumnWidth('C', 128)
lc.setColumnWidth('D', 150)
lc.setColumnWidth('E', 200)
# Add the spreadsheet to the list of loading conditions of the ship
lcs = ship.LoadConditions[:]
lcs.append(lc.Name)
ship.LoadConditions = lcs
ship.Proxy.cleanLoadConditions(ship)
# Recompute to take the changes
App.activeDocument().recompute()
return lc

View File

@ -0,0 +1,77 @@
#***************************************************************************
#* *
#* 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 FreeCAD as App
import FreeCADGui as Gui
from PySide import QtGui
import Tools
READ_ONLY_FOREGROUND = (0.5, 0.5, 0.5)
READ_ONLY_BACKGROUND = (0.9, 0.9, 0.9)
def load():
"""Directly create the load condition"""
# Check that a ship has been selected
ship = None
selObjs = Gui.Selection.getSelection()
if not selObjs:
msg = QtGui.QApplication.translate(
"ship_console",
"A ship instance must be selected before using this tool (no"
" objects selected)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
return
for i in range(len(selObjs)):
obj = selObjs[i]
props = obj.PropertiesList
try:
props.index("IsShip")
except ValueError:
continue
if obj.IsShip:
if ship:
msg = QtGui.QApplication.translate(
"ship_console",
"More than one ship have been selected (the extra"
" ships will be ignored)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintWarning(msg + '\n')
break
ship = obj
if not ship:
msg = QtGui.QApplication.translate(
"ship_console",
"A ship instance must be selected before using this tool (no"
" valid ship found at the selected objects)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
return
Tools.createLoadCondition(ship)

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -26,6 +26,7 @@ import FreeCADGui as Gui
import Units
from PySide import QtGui, QtCore
import Preview
import Tools
import Instance
from shipUtils import Paths
import shipUtils.Units as USys
@ -40,19 +41,16 @@ class TaskPanel:
def accept(self):
"""Create the ship instance"""
self.preview.clean()
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Ship")
ship = Instance.Ship(obj, self.solids)
Instance.ViewProviderShip(obj.ViewObject)
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.length = self.widget(QtGui.QLineEdit, "Length")
form.breadth = self.widget(QtGui.QLineEdit, "Breadth")
form.draft = self.widget(QtGui.QLineEdit, "Draft")
obj.Length = Locale.fromString(form.length.text())
obj.Breadth = Locale.fromString(form.breadth.text())
obj.Draft = Locale.fromString(form.draft.text())
App.ActiveDocument.recompute()
Tools.createShip(self.solids,
Locale.fromString(form.length.text()),
Locale.fromString(form.breadth.text()),
Locale.fromString(form.draft.text()))
return True
def reject(self):

View File

@ -0,0 +1,65 @@
#***************************************************************************
#* *
#* Copyright (c) 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 FreeCAD as App
import Instance
def createShip(solids, L, B, T):
"""Create a new ship instance
Position arguments:
solids -- List of hull solid shapes
L -- Ship length between perpendiculars
B -- Ship Breadth
T -- Ship design draft
Returned value:
The new ship object
The solids can be easily extracted from an already existing object. For
instance, to get the solids from the selected object simply type the
following command:
solids = Gui.ActiveDocument.ActiveObject.Object.Shape.Solids
Regarding the Lenght, Breadth, and Draft, it is strongly recommended to use
Units.parseQuantity method, e.g. The following obfuscated code snippet build
such variables:
import Units
L = Units.parseQuantity("25.5 m")
B = Units.parseQuantity("3.9 m")
T = Units.parseQuantity("1.0 m")
"""
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Ship")
ship = Instance.Ship(obj, solids)
Instance.ViewProviderShip(obj.ViewObject)
obj.Length = L
obj.Breadth = B
obj.Draft = T
App.ActiveDocument.recompute()
return obj

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -25,6 +25,7 @@ import FreeCAD as App
import FreeCADGui as Gui
import Units
from PySide import QtGui, QtCore
import Tools
import TankInstance as Instance
from shipUtils import Paths
import shipUtils.Units as USys
@ -40,18 +41,9 @@ class TaskPanel:
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.ship = self.widget(QtGui.QComboBox, "Ship")
# Create the object
ship = self.ships[form.ship.currentIndex()]
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Tank")
tank = Instance.Tank(obj, self.solids, ship)
Instance.ViewProviderTank(obj.ViewObject)
Tools.createTank(self.solids, ship)
# Set it as a child of the ship
tanks = ship.Tanks[:]
tanks.append(obj.Name)
ship.Tanks = tanks
App.ActiveDocument.recompute()
return True
def reject(self):

View File

@ -0,0 +1,56 @@
#***************************************************************************
#* *
#* Copyright (c) 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 FreeCAD as App
import Units
import TankInstance as Instance
def createTank(solids, ship):
"""Create a new tank instance
Position arguments:
solids -- List of solid shapes
ship -- Ship owner
Returned value:
The new tank object
The tool will claim the new tank as a child of the ship object. Please do
not remove the partner ship object before removing this new tank before.
"""
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Tank")
tank = Instance.Tank(obj, solids, ship)
Instance.ViewProviderTank(obj.ViewObject)
# Set it as a child of the ship
tanks = ship.Tanks[:]
tanks.append(obj.Name)
ship.Tanks = tanks
ship.Proxy.cleanWeights(ship)
ship.Proxy.cleanTanks(ship)
ship.Proxy.cleanLoadConditions(ship)
App.ActiveDocument.recompute()
return obj

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -25,6 +25,7 @@ import FreeCAD as App
import FreeCADGui as Gui
import Units
from PySide import QtGui, QtCore
import Tools
import WeightInstance as Instance
from shipUtils import Paths
import shipUtils.Units as USys
@ -42,35 +43,10 @@ class TaskPanel:
form.ship = self.widget(QtGui.QComboBox, "Ship")
form.weight = self.widget(QtGui.QLineEdit, "Weight")
# Create the object
ship = self.ships[form.ship.currentIndex()]
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Weight")
weight = Instance.Weight(obj, self.shapes, ship)
Instance.ViewProviderWeight(obj.ViewObject)
density = Units.parseQuantity(Locale.fromString(form.weight.text()))
# Set the mass/density
m_unit = USys.getMassUnits()
l_unit = USys.getLengthUnits()
qty = Units.parseQuantity(Locale.fromString(form.weight.text()))
if self.elem_type == 1:
w_unit = m_unit
obj.Mass = qty.getValueAs(w_unit).Value
elif self.elem_type == 2:
w_unit = m_unit + '/' + l_unit
obj.LineDens = qty.getValueAs(w_unit).Value
elif self.elem_type == 3:
w_unit = m_unit + '/' + l_unit + '^2'
obj.AreaDens = qty.getValueAs(w_unit).Value
elif self.elem_type == 4:
w_unit = m_unit + '/' + l_unit + '^3'
obj.Dens = qty.getValueAs(w_unit).Value
# Set it as a child of the ship
weights = ship.Weights[:]
weights.append(obj.Name)
ship.Weights = weights
App.ActiveDocument.recompute()
Tools.createWeight(self.shapes, ship, density)
return True
def reject(self):

View File

@ -0,0 +1,91 @@
#***************************************************************************
#* *
#* Copyright (c) 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 FreeCAD as App
import Units
import WeightInstance as Instance
import shipUtils.Units as USys
def createWeight(shapes, ship, density):
"""Create a new weight instance
Position arguments:
shapes -- List of shapes of the weight
ship -- Ship owner
density -- Density of the object. 4 possibilities are considered here:
* Density as Mass/Volume: then the weight will be considered as a
volumetric object. Used for blocks or similar.
* Density as Mass/Area: then the weight will be considered as an area
element. Used for structural shells.
* Density as Mass/Length: then the weight will be cosidered as a linear
element. Used for linear structural reinforcements.
* Mass: Then a punctual mass will be considered. Used for complex
weights, like engines or other machines.
Returned value:
The new weight object
It is strongly recommended to pass just shapes matching with the provided
density, e.g. don't use volumetric shapes with a linear density value (kg/m)
The tool will claim the new weight as a child of the ship object. Please do
not remove the partner ship object before removing this new weight before.
"""
# Create the object
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Weight")
weight = Instance.Weight(obj, shapes, ship)
Instance.ViewProviderWeight(obj.ViewObject)
# Setup the mass/density value
m_unit = "kg"
l_unit = "m"
m_qty = Units.Quantity(1, Units.Mass)
l_qty = Units.Quantity(1, Units.Length)
a_qty = Units.Quantity(1, Units.Area)
v_qty = Units.Quantity(1, Units.Volume)
if density.Unit == m_qty.Unit:
w_unit = m_unit
obj.Mass = density.getValueAs(w_unit).Value
elif density.Unit == (m_qty / l_qty).Unit:
w_unit = m_unit + '/' + l_unit
obj.LineDens = density.getValueAs(w_unit).Value
elif density.Unit == (m_qty / a_qty).Unit:
w_unit = m_unit + '/' + l_unit + '^2'
obj.AreaDens = density.getValueAs(w_unit).Value
elif density.Unit == (m_qty / v_qty).Unit:
w_unit = m_unit + '/' + l_unit + '^3'
obj.Dens = density.getValueAs(w_unit).Value
# Set it as a child of the ship
weights = ship.Weights[:]
weights.append(obj.Name)
ship.Weights = weights
ship.Proxy.cleanWeights(ship)
ship.Proxy.cleanTanks(ship)
ship.Proxy.cleanLoadConditions(ship)
App.ActiveDocument.recompute()
return obj

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -0,0 +1,106 @@
#***************************************************************************
#* *
#* 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 os
import math
from PySide import QtGui, QtCore
import FreeCAD
import FreeCADGui
import Spreadsheet
from shipUtils import Paths
class Plot(object):
def __init__(self, roll, gz, draft, trim):
""" Plot the GZ curve
Position arguments:
roll -- List of roll angles (in degrees).
gz -- List of GZ values (in meters).
draft -- List of equilibrium drafts (in meters).
trim -- List of equilibrium trim angles (in degrees).
"""
self.plot(roll, gz)
self.spreadSheet(roll, gz, draft, trim)
def plot(self, roll, gz):
""" Plot the GZ curve.
Position arguments:
roll -- List of roll angles (in degrees).
gz -- List of GZ values (in meters).
"""
try:
import Plot
plt = Plot.figure('GZ')
except ImportError:
msg = QtGui.QApplication.translate(
"ship_console",
"Plot module is disabled, so I cannot perform the plot",
None,
QtGui.QApplication.UnicodeUTF8)
FreeCAD.Console.PrintWarning(msg + '\n')
return True
gz_plot = Plot.plot(roll, gz, 'GZ curve')
gz_plot.line.set_linestyle('-')
gz_plot.line.set_linewidth(1.0)
gz_plot.line.set_color((0.0, 0.0, 0.0))
ax = Plot.axes()
Plot.xlabel(r'$\phi \; [\mathrm{deg}]$')
Plot.ylabel(r'$GZ \; [\mathrm{m}]$')
ax.xaxis.label.set_fontsize(20)
ax.yaxis.label.set_fontsize(20)
Plot.grid(True)
plt.update()
return False
def spreadSheet(self, roll, gz, draft, trim):
""" Create a Spreadsheet with the results
Position arguments:
roll -- List of roll angles (in degrees).
gz -- List of GZ values (in meters).
draft -- List of equilibrium drafts (in meters).
trim -- List of equilibrium trim angles (in degrees).
"""
s = FreeCAD.activeDocument().addObject('Spreadsheet::Sheet',
'GZ')
# Print the header
s.set("A1", "roll [deg]")
s.set("B1", "GZ [m]")
s.set("C1", "draft [m]")
s.set("D1", "trim [deg]")
# Print the data
for i in range(len(roll)):
s.set("A{}".format(i + 2), str(roll[i]))
s.set("B{}".format(i + 2), str(gz[i]))
s.set("C{}".format(i + 2), str(draft[i]))
s.set("D{}".format(i + 2), str(trim[i]))
# Recompute
FreeCAD.activeDocument().recompute()

View File

@ -0,0 +1,345 @@
#***************************************************************************
#* *
#* 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 FreeCAD as App
import FreeCADGui as Gui
import Units
from PySide import QtGui, QtCore
import PlotAux
import Tools
from shipUtils import Paths
import shipUtils.Units as USys
import shipUtils.Locale as Locale
class TaskPanel:
def __init__(self):
self.ui = Paths.modulePath() + "/shipGZ/TaskPanel.ui"
def accept(self):
if self.lc is None:
return False
self.save()
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.angle = self.widget(QtGui.QLineEdit, "Angle")
form.n_points = self.widget(QtGui.QSpinBox, "NumPoints")
form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim")
roll = Units.Quantity(Locale.fromString(form.angle.text()))
n_points = form.n_points.value()
var_trim = form.var_trim.isChecked()
rolls = []
for i in range(n_points):
rolls.append(roll * i / float(n_points - 1))
points = Tools.gz(self.lc, rolls, var_trim)
gzs = []
drafts = []
trims = []
for p in points:
gzs.append(p[0].getValueAs('m').Value)
drafts.append(p[1].getValueAs('m').Value)
trims.append(p[2].getValueAs('deg').Value)
PlotAux.Plot(rolls, gzs, drafts, trims)
return True
def reject(self):
return True
def clicked(self, index):
pass
def open(self):
pass
def needsFullSpace(self):
return True
def isAllowedAlterSelection(self):
return False
def isAllowedAlterView(self):
return True
def isAllowedAlterDocument(self):
return False
def helpRequested(self):
pass
def setupUi(self):
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.angle = self.widget(QtGui.QLineEdit, "Angle")
form.n_points = self.widget(QtGui.QSpinBox, "NumPoints")
form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim")
self.form = form
if self.initValues():
return True
self.retranslateUi()
def getMainWindow(self):
toplevel = QtGui.qApp.topLevelWidgets()
for i in toplevel:
if i.metaObject().className() == "Gui::MainWindow":
return i
raise RuntimeError("No main window found")
def widget(self, class_id, name):
"""Return the selected widget.
Keyword arguments:
class_id -- Class identifier
name -- Name of the widget
"""
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
return form.findChild(class_id, name)
def initValues(self):
""" Set initial values for fields
"""
# Look for selected loading conditions (Spreadsheets)
self.lc = None
selObjs = Gui.Selection.getSelection()
if not selObjs:
msg = QtGui.QApplication.translate(
"ship_console",
"A loading condition instance must be selected before using"
" this tool (no objects selected)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
return True
for i in range(len(selObjs)):
obj = selObjs[i]
try:
if obj.TypeId != 'Spreadsheet::Sheet':
continue
except ValueError:
continue
# Check if it is a Loading condition:
# B1 cell must be a ship
# B2 cell must be the loading condition itself
doc = App.ActiveDocument
try:
if obj not in doc.getObjectsByLabel(obj.get('B2')):
continue
ships = doc.getObjectsByLabel(obj.get('B1'))
if len(ships) != 1:
if len(ships) == 0:
msg = QtGui.QApplication.translate(
"ship_console",
"Wrong Ship label! (no instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
obj.get('B1')))
else:
msg = QtGui.QApplication.translate(
"ship_console",
"Ambiguous Ship label! ({} instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ships),
obj.get('B1')))
continue
ship = ships[0]
if ship is None or not ship.PropertiesList.index("IsShip"):
continue
except ValueError:
continue
# Let's see if several loading conditions have been selected (and
# prompt a warning)
if self.lc:
msg = QtGui.QApplication.translate(
"ship_console",
"More than one loading condition have been selected (the"
" extra loading conditions will be ignored)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintWarning(msg + '\n')
break
self.lc = obj
self.ship = ship
if not self.lc:
msg = QtGui.QApplication.translate(
"ship_console",
"A loading condition instance must be selected before using"
" this tool (no valid loading condition found at the selected"
" objects)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n')
return True
# We have a valid loading condition, let's set the initial field values
angle_format = USys.getAngleFormat()
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.angle = self.widget(QtGui.QLineEdit, "Angle")
form.n_points = self.widget(QtGui.QSpinBox, "NumPoints")
form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim")
form.angle.setText(Locale.toString(angle_format.format(90.0)))
# Try to use saved values
props = self.ship.PropertiesList
try:
props.index("GZAngle")
form.angle.setText(Locale.toString(angle_format.format(
self.ship.GZAngle.getValueAs(
USys.getAngleUnits()).Value)))
except:
pass
try:
props.index("GZNumPoints")
form.n_points.setValue(self.ship.GZNumPoints)
except ValueError:
pass
try:
props.index("GZVariableTrim")
if self.ship.GZVariableTrim:
form.var_trim.setCheckState(QtCore.Qt.Checked)
else:
form.var_trim.setCheckState(QtCore.Qt.Unchecked)
except ValueError:
pass
return False
def retranslateUi(self):
""" Set user interface locale strings. """
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.setWindowTitle(QtGui.QApplication.translate(
"ship_gz",
"Plot the GZ curve",
None,
QtGui.QApplication.UnicodeUTF8))
self.widget(QtGui.QLabel, "AngleLabel").setText(
QtGui.QApplication.translate(
"ship_gz",
"Maximum angle",
None,
QtGui.QApplication.UnicodeUTF8))
self.widget(QtGui.QLabel, "NumPointsLabel").setText(
QtGui.QApplication.translate(
"ship_gz",
"Number of points",
None,
QtGui.QApplication.UnicodeUTF8))
self.widget(QtGui.QCheckBox, "VariableTrim").setText(
QtGui.QApplication.translate(
"ship_gz",
"Variable trim",
None,
QtGui.QApplication.UnicodeUTF8))
self.widget(QtGui.QCheckBox, "VariableTrim").setToolTip(
QtGui.QApplication.translate(
"ship_gz",
"The ship will be rotated to the equilibrium trim angle for" + \
" each roll angle. It will significantly increase the" + \
" required computing time",
None,
QtGui.QApplication.UnicodeUTF8))
def save(self):
""" Saves the data into ship instance. """
mw = self.getMainWindow()
form = mw.findChild(QtGui.QWidget, "TaskPanel")
form.angle = self.widget(QtGui.QLineEdit, "Angle")
form.n_points = self.widget(QtGui.QSpinBox, "NumPoints")
form.var_trim = self.widget(QtGui.QCheckBox, "VariableTrim")
angle = Units.Quantity(Locale.fromString(
form.angle.text())).getValueAs('deg').Value
n_points = form.n_points.value()
var_trim = form.var_trim.isChecked()
props = self.ship.PropertiesList
try:
props.index("GZAngle")
except ValueError:
try:
tooltip = str(QtGui.QApplication.translate(
"ship_gz",
"GZ curve tool angle selected [deg]",
None,
QtGui.QApplication.UnicodeUTF8))
except:
tooltip = "GZ curve tool angle selected [deg]"
self.ship.addProperty("App::PropertyAngle",
"GZAngle",
"Ship",
tooltip)
self.ship.GZAngle = '{} deg'.format(angle)
try:
props.index("GZNumPoints")
except ValueError:
try:
tooltip = str(QtGui.QApplication.translate(
"ship_gz",
"GZ curve tool number of points selected",
None,
QtGui.QApplication.UnicodeUTF8))
except:
tooltip = "GZ curve tool number of points selected"
self.ship.addProperty("App::PropertyInteger",
"GZNumPoints",
"Ship",
tooltip)
self.ship.GZNumPoints = n_points
try:
props.index("GZVariableTrim")
except ValueError:
try:
tooltip = str(QtGui.QApplication.translate(
"ship_gz",
"GZ curve tool variable trim angle selection",
None,
QtGui.QApplication.UnicodeUTF8))
except:
tooltip = "GZ curve tool variable trim angle selection"
self.ship.addProperty("App::PropertyBool",
"GZVariableTrim",
"Ship",
tooltip)
self.ship.GZVariableTrim = var_trim
def createTask():
panel = TaskPanel()
Gui.Control.showDialog(panel)
if panel.setupUi():
Gui.Control.closeDialog(panel)
return None
return panel

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TaskPanel</class>
<widget class="QWidget" name="TaskPanel">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>260</width>
<height>256</height>
</rect>
</property>
<property name="windowTitle">
<string>GZ stability curve</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="NumPointsLabel">
<property name="text">
<string>Number of points</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="NumPoints">
<property name="minimum">
<number>2</number>
</property>
<property name="value">
<number>11</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="AngleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Maximum angle</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::InputField" name="Angle"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="VariableTrim">
<property name="text">
<string>Variable Trim angle</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header location="global">Gui/InputField.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,316 @@
#***************************************************************************
#* *
#* 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 FreeCAD as App
import FreeCADGui as Gui
from FreeCAD import Vector, Matrix, Placement
import Part
import Units
import Instance as ShipInstance
import WeightInstance
import TankInstance
from shipHydrostatics import Tools as Hydrostatics
G = Units.parseQuantity("9.81 m/s^2")
MAX_EQUILIBRIUM_ITERS = 10
DENS = Units.parseQuantity("1025 kg/m^3")
TRIM_RELAX_FACTOR = 10.0
def solve(ship, weights, tanks, rolls, var_trim=True):
"""Compute the ship GZ stability curve
Position arguments:
ship -- Ship object
weights -- List of weights to consider
tanks -- List of tanks to consider (each one should be a tuple with the
tank instance, the density of the fluid inside, and the filling level ratio)
rolls -- List of roll angles
Keyword arguments:
var_trim -- True if the equilibrium trim should be computed for each roll
angle, False if null trim angle can be used instead.
Returned value:
List of GZ curve points. Each point contains the GZ stability length, the
equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is
False)
"""
# Get the unloaded weight (ignoring the tanks for the moment).
W = Units.parseQuantity("0 kg")
mom_x = Units.parseQuantity("0 kg*m")
mom_y = Units.parseQuantity("0 kg*m")
mom_z = Units.parseQuantity("0 kg*m")
for w in weights:
W += w.Proxy.getMass(w)
m = w.Proxy.getMoment(w)
mom_x += m[0]
mom_y += m[1]
mom_z += m[2]
COG = Vector(mom_x / W, mom_y / W, mom_z / W)
W = W * G
# Get the tanks weight
TW = Units.parseQuantity("0 kg")
VOLS = []
for t in tanks:
# t[0] = tank object
# t[1] = load density
# t[2] = filling level
vol = t[0].Proxy.getVolume(t[0], t[2])
VOLS.append(vol)
TW += vol * t[1]
TW = TW * G
points = []
for i,roll in enumerate(rolls):
App.Console.PrintMessage("{0} / {1}\n".format(i + 1, len(rolls)))
point = solve_point(W, COG, TW, VOLS,
ship, tanks, roll, var_trim)
if point is None:
return []
points.append(point)
return points
def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True):
""" Compute the ship GZ value.
@param W Empty ship weight.
@param COG Empty ship Center of mass.
@param TW Tanks weights.
@param VOLS List of tank volumes.
@param tanks Considered tanks.
@param roll Roll angle.
@param var_trim True if the trim angle should be recomputed at each roll
angle, False otherwise.
@return GZ value, equilibrium draft, and equilibrium trim angle (0 if
variable trim has not been requested)
"""
# Look for the equilibrium draft (and eventually the trim angle too)
max_draft = Units.Quantity(ship.Shape.BoundBox.ZMax, Units.Length)
draft = ship.Draft
max_disp = Units.Quantity(ship.Shape.Volume, Units.Volume) * DENS * G
if max_disp < W + TW:
msg = QtGui.QApplication.translate(
"ship_console",
"Too much weight! The ship will never displace water enough",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + ' ({} vs. {})\n'.format(
(max_disp / G).UserString, ((W + TW) / G).UserString))
return None
trim = Units.parseQuantity("0 deg")
for i in range(MAX_EQUILIBRIUM_ITERS):
# Get the displacement, and the bouyance application point
disp, B, _ = Hydrostatics.displacement(ship,
draft,
roll,
trim)
disp *= G
# Add the tanks effect on the center of gravity
mom_x = Units.Quantity(COG.x, Units.Length) * W
mom_y = Units.Quantity(COG.y, Units.Length) * W
mom_z = Units.Quantity(COG.z, Units.Length) * W
for i,t in enumerate(tanks):
tank_weight = VOLS[i] * t[1] * G
tank_cog = t[0].Proxy.getCoG(t[0], VOLS[i], roll, trim)
mom_x += Units.Quantity(tank_cog.x, Units.Length) * tank_weight
mom_y += Units.Quantity(tank_cog.y, Units.Length) * tank_weight
mom_z += Units.Quantity(tank_cog.z, Units.Length) * tank_weight
cog_x = mom_x / (W + TW)
cog_y = mom_y / (W + TW)
cog_z = mom_z / (W + TW)
# Compute the errors
draft_error = -((disp - W - TW) / max_disp).Value
R_x = cog_x - Units.Quantity(B.x, Units.Length)
R_y = cog_y - Units.Quantity(B.y, Units.Length)
R_z = cog_z - Units.Quantity(B.z, Units.Length)
if not var_trim:
trim_error = 0.0
else:
trim_error = -TRIM_RELAX_FACTOR * R_x / ship.Length
# Check if we can tolerate the errors
if abs(draft_error) < 0.01 and abs(trim_error) < 0.1:
break
# Get the new draft and trim
draft += draft_error * max_draft
trim += trim_error * Units.Degree
# GZ should be provided in the Free surface oriented frame of reference
c = math.cos(roll.getValueAs('rad'))
s = math.sin(roll.getValueAs('rad'))
return c * R_y - s * R_z, draft, trim
def gz(lc, rolls, var_trim=True):
"""Compute the ship GZ stability curve
Position arguments:
lc -- Load condition spreadsheet
rolls -- List of roll angles to compute
Keyword arguments:
var_trim -- True if the equilibrium trim should be computed for each roll
angle, False if null trim angle can be used instead.
Returned value:
List of GZ curve points. Each point contains the GZ stability length, the
equilibrium draft, and the equilibrium trim angle (0 deg if var_trim is
False)
"""
# B1 cell must be a ship
# B2 cell must be the loading condition itself
doc = lc.Document
try:
if lc not in doc.getObjectsByLabel(lc.get('B2')):
return[]
ships = doc.getObjectsByLabel(lc.get('B1'))
if len(ships) != 1:
if len(ships) == 0:
msg = QtGui.QApplication.translate(
"ship_console",
"Wrong Ship label! (no instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
lc.get('B1')))
else:
msg = QtGui.QApplication.translate(
"ship_console",
"Ambiguous Ship label! ({} instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ships),
lc.get('B1')))
return[]
ship = ships[0]
if ship is None or not ship.PropertiesList.index("IsShip"):
return[]
except ValueError:
return[]
# Extract the weights and the tanks
weights = []
index = 6
while True:
try:
ws = doc.getObjectsByLabel(lc.get('A{}'.format(index)))
except ValueError:
break
index += 1
if len(ws) != 1:
if len(ws) == 0:
msg = QtGui.QApplication.translate(
"ship_console",
"Wrong Weight label! (no instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
lc.get('A{}'.format(index - 1))))
else:
msg = QtGui.QApplication.translate(
"ship_console",
"Ambiguous Weight label! ({} instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ws),
lc.get('A{}'.format(index - 1))))
continue
w = ws[0]
try:
if w is None or not w.PropertiesList.index("IsWeight"):
msg = QtGui.QApplication.translate(
"ship_console",
"Invalid Weight! (the object labeled as"
"'{}' is not a weight)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ws),
lc.get('A{}'.format(index - 1))))
continue
except ValueError:
continue
weights.append(w)
tanks = []
index = 6
while True:
try:
ts = doc.getObjectsByLabel(lc.get('C{}'.format(index)))
dens = float(lc.get('D{}'.format(index)))
level = float(lc.get('E{}'.format(index)))
dens = Units.parseQuantity("{} kg/m^3".format(dens))
except ValueError:
break
index += 1
if len(ts) != 1:
if len(ts) == 0:
msg = QtGui.QApplication.translate(
"ship_console",
"Wrong Tank label! (no instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
lc.get('C{}'.format(index - 1))))
else:
msg = QtGui.QApplication.translate(
"ship_console",
"Ambiguous Tank label! ({} instances labeled as"
"'{}' found)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ts),
lc.get('C{}'.format(index - 1))))
continue
t = ts[0]
try:
if t is None or not t.PropertiesList.index("IsTank"):
msg = QtGui.QApplication.translate(
"ship_console",
"Invalid Tank! (the object labeled as"
"'{}' is not a tank)",
None,
QtGui.QApplication.UnicodeUTF8)
App.Console.PrintError(msg + '\n'.format(
len(ws),
lc.get('C{}'.format(index - 1))))
continue
except ValueError:
continue
tanks.append((t, dens, level))
return solve(ship, weights, tanks, rolls, var_trim)

View File

@ -0,0 +1,29 @@
#***************************************************************************
#* *
#* 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 TaskPanel
def load():
""" Loads the tool """
TaskPanel.createTask()

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -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

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -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

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -22,45 +22,166 @@
#***************************************************************************
import math
from FreeCAD import Vector
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
def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30):
""" Compute the 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)
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,
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()
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 []
# We will take a duplicate of ship shape in order to conviniently
# manipulate it
shape = ship.Shape.copy()
shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value))
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)
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 = [[xmin / Units.Metre.Value, 0.0]]
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
@ -71,396 +192,350 @@ def areas(ship, draft, roll=0.0, trim=0.0, yaw=0.0, n=30):
for i in range(1, n - 1):
App.Console.PrintMessage("{0} / {1}\n".format(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)
try:
box = Part.makeBox(1.5 * L + x, 3.0 * B, -bbox.ZMin, p)
f = Part.Face(shape.slice(Vector(1,0,0), x))
except Part.OCCError:
areas.append([x, area])
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
# Compute the common part with ship
for s in shape.Solids:
try:
common = box.common(s)
except Part.OCCError:
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 App.Base.FreeCADError:
continue
# Divide the solid by faces and compute only the well 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
# It is a valid face, so we can add this area
area = area + f.Area / Units.Metre.Value**2
# Destroy the last generated object
App.ActiveDocument.removeObject(
App.ActiveDocument.Objects[-1].Name)
areas.append([x / Units.Metre.Value, area])
# 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([xmax / Units.Metre.Value, 0.0])
areas.append((Units.Quantity(xmax, Units.Length),
Units.Quantity(0.0, Units.Area)))
App.Console.PrintMessage("Done!\n")
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()
if draft is None:
draft = ship.Draft
shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value))
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)
bbox = shape.BoundBox
xmin = bbox.XMin
xmax = bbox.XMax
# Create the "sea" box to intersect the ship
L = xmax - xmin
B = bbox.YMax - bbox.YMin
p = Vector(-1.5*L, -1.5*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, Vector(), 0.0]
shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim)
shape = getUnderwaterSide(shape)
vol = 0.0
cog = Vector()
for solid in shape.Solids:
# Compute the common part of the "sea" with the ship
try:
common = box.common(solid)
except Part.OCCError:
continue
# Get the data
vol = vol + common.Volume / Units.Metre.Value**3
for s in common.Solids:
sCoG = s.CenterOfMass
cog.x = cog.x + sCoG.x * s.Volume / Units.Metre.Value**4
cog.y = cog.y + sCoG.y * s.Volume / Units.Metre.Value**4
cog.z = cog.z + sCoG.z * s.Volume / Units.Metre.Value**4
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
# Undo the 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 the computed 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
shape = shape.copy()
shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value))
shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim)
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
xmin = bbox.XMin
xmax = bbox.XMax
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)
# Create the "sea" box
L = xmax - xmin
B = bbox.YMax - bbox.YMin
p = Vector(-1.5 * L, -1.5 * 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()
shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value))
shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim)
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
# Create the "sea" box
L = xmax - xmin
B = bbox.YMax - bbox.YMin
p = Vector(-1.5 * L, -1.5 * 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
Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin)
# Create the "sea" box
L = xmax - xmin
B = bbox.YMax - bbox.YMin
p = Vector(-1.5 * L, -1.5 * 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

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
@ -44,7 +44,7 @@ class TaskPanel:
App.open(path + "wigley.fcstd")
elif(form.ship.currentIndex() == 2): # s60 (Katamaran)
App.open(path + "s60_katamaran.fcstd")
elif(form.ship.currentIndex() == 2): # Wigley (Katamaran)
elif(form.ship.currentIndex() == 3): # Wigley (Katamaran)
App.open(path + "wigley_katamaran.fcstd")
return True

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -1,6 +1,6 @@
#***************************************************************************
#* *
#* Copyright (c) 2011, 2012 *
#* Copyright (c) 2011, 2016 *
#* Jose Luis Cercos Pita <jlcercos@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *

View File

@ -26,9 +26,11 @@
<Component Id="CompModShip" Guid="4ddaaa92-1ccc-4462-ab58-606082aa277e" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="ShipInitGuiPy" Name="InitGui.py" DiskId="1" />
<File Id="ShipInstance" Name="Instance.py" DiskId="1" />
<File Id="ShipConsoleInterface" Name="Ship.py" DiskId="1" />
<File Id="ShipGuiPy" Name="ShipGui.py" DiskId="1" />
<File Id="ShipResources" Name="Ship_rc.py" DiskId="1" />
<File Id="ShipTankInstance" Name="TankInstance.py" DiskId="1" />
<File Id="ShipWeightInstance" Name="WeightInstance.py" DiskId="1" />
</Component>
<Directory Id="ModShipResources" Name="resources" FileSource="../../Mod/Ship/resources" >
<Directory Id="ModShipExamples" Name="examples" FileSource="../../Mod/Ship/resources/examples" >
@ -49,12 +51,53 @@
<File Id="shipAreasCurve05" Name="TaskPanel.ui" />
</Component>
</Directory>
<Directory Id="ModshipCapacityCurve" Name="shipCapacityCurve" FileSource="../../Mod/Ship/shipCapacityCurve" >
<Component Id="CompModshipCapacityCurve" Guid="1714cf1c-728a-4989-8ea0-622043f6c9b3" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipCapacityCurve01" Name="__init__.py" />
<File Id="shipCapacityCurve02" Name="PlotAux.py" />
<File Id="shipCapacityCurve03" Name="TaskPanel.py" />
<File Id="shipCapacityCurve04" Name="TaskPanel.ui" />
<File Id="shipCapacityCurve05" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipCreateLoadCondition" Name="shipCreateLoadCondition" FileSource="../../Mod/Ship/shipCreateLoadCondition" >
<Component Id="CompModshipCreateLoadCondition" Guid="821f7073-b0aa-47c5-ada4-7e4c045c06e6" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipCreateLoadCondition01" Name="__init__.py" />
<File Id="shipCreateLoadCondition02" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipCreateShip" Name="shipCreateShip" FileSource="../../Mod/Ship/shipCreateShip" >
<Component Id="CompModshipCreateShip" Guid="7492bfb5-96d8-4555-8cc0-682c79a779ac" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipCreateShip01" Name="__init__.py" />
<File Id="shipCreateShip02" Name="Preview.py" />
<File Id="shipCreateShip03" Name="TaskPanel.py" />
<File Id="shipCreateShip04" Name="TaskPanel.ui" />
<File Id="shipCreateShip05" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipCreateTank" Name="shipCreateTank" FileSource="../../Mod/Ship/shipCreateTank" >
<Component Id="CompModshipCreateTank" Guid="eed2147f-2a45-4c12-aac6-bffae3ac1253" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipCreateTank01" Name="__init__.py" />
<File Id="shipCreateTank02" Name="TaskPanel.py" />
<File Id="shipCreateTank03" Name="TaskPanel.ui" />
<File Id="shipCreateTank04" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipCreateWeight" Name="shipCreateWeight" FileSource="../../Mod/Ship/shipCreateWeight" >
<Component Id="CompModshipCreateWeight" Guid="05b5db99-5bcc-421a-83e5-2279cb4f9ab0" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipCreateWeight01" Name="__init__.py" />
<File Id="shipCreateWeight02" Name="TaskPanel.py" />
<File Id="shipCreateWeight03" Name="TaskPanel.ui" />
<File Id="shipCreateWeight04" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipGZ" Name="shipGZ" FileSource="../../Mod/Ship/shipGZ" >
<Component Id="CompModshipGZ" Guid="62099cb8-1ba3-45df-b012-42f6bb80fb1b" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipGZ01" Name="__init__.py" />
<File Id="shipGZ02" Name="PlotAux.py" />
<File Id="shipGZ03" Name="TaskPanel.py" />
<File Id="shipGZ04" Name="TaskPanel.ui" />
<File Id="shipGZ05" Name="Tools.py" />
</Component>
</Directory>
<Directory Id="ModshipHydrostatics" Name="shipHydrostatics" FileSource="../../Mod/Ship/shipHydrostatics" >
@ -85,6 +128,7 @@
<Directory Id="ModshipUtils" Name="shipUtils" FileSource="../../Mod/Ship/shipUtils" >
<Component Id="CompModshipUtils" Guid="7fca3f4d-861a-44fb-ab2e-cd256e3ba9b3" Win64='$(var.Win_64)' KeyPath="yes">
<File Id="shipUtils01" Name="__init__.py" />
<File Id="shipUtils02" Name="Locale.py" />
<File Id="shipUtils02" Name="Math.py" />
<File Id="shipUtils03" Name="Paths.py" />
<File Id="shipUtils04" Name="Units.py" />