commit
173d197fe0
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
39
src/Mod/Ship/Ship.py
Normal 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
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
660
src/Mod/Ship/resources/icons/Ship_LoadCondition.svg
Normal file
660
src/Mod/Ship/resources/icons/Ship_LoadCondition.svg
Normal 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 |
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
60
src/Mod/Ship/shipCapacityCurve/Tools.py
Normal file
60
src/Mod/Ship/shipCapacityCurve/Tools.py
Normal 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
|
|
@ -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 *
|
||||
|
|
124
src/Mod/Ship/shipCreateLoadCondition/Tools.py
Normal file
124
src/Mod/Ship/shipCreateLoadCondition/Tools.py
Normal 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
|
77
src/Mod/Ship/shipCreateLoadCondition/__init__.py
Normal file
77
src/Mod/Ship/shipCreateLoadCondition/__init__.py
Normal 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)
|
|
@ -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 *
|
||||
|
|
|
@ -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):
|
||||
|
|
65
src/Mod/Ship/shipCreateShip/Tools.py
Normal file
65
src/Mod/Ship/shipCreateShip/Tools.py
Normal 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
|
|
@ -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 *
|
||||
|
|
|
@ -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):
|
||||
|
|
56
src/Mod/Ship/shipCreateTank/Tools.py
Normal file
56
src/Mod/Ship/shipCreateTank/Tools.py
Normal 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
|
|
@ -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 *
|
||||
|
|
|
@ -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):
|
||||
|
|
91
src/Mod/Ship/shipCreateWeight/Tools.py
Normal file
91
src/Mod/Ship/shipCreateWeight/Tools.py
Normal 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
|
|
@ -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 *
|
||||
|
|
106
src/Mod/Ship/shipGZ/PlotAux.py
Normal file
106
src/Mod/Ship/shipGZ/PlotAux.py
Normal 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()
|
345
src/Mod/Ship/shipGZ/TaskPanel.py
Normal file
345
src/Mod/Ship/shipGZ/TaskPanel.py
Normal 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
|
75
src/Mod/Ship/shipGZ/TaskPanel.ui
Normal file
75
src/Mod/Ship/shipGZ/TaskPanel.ui
Normal 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>
|
316
src/Mod/Ship/shipGZ/Tools.py
Normal file
316
src/Mod/Ship/shipGZ/Tools.py
Normal 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)
|
29
src/Mod/Ship/shipGZ/__init__.py
Normal file
29
src/Mod/Ship/shipGZ/__init__.py
Normal 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()
|
|
@ -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()
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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 *
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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,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" />
|
||||
|
|
Loading…
Reference in New Issue
Block a user