From 0a4a8191a27a519a1c189047664e5efc1665c49b Mon Sep 17 00:00:00 2001 From: Jose Luis Cercos Pita Date: Fri, 22 Jan 2016 13:17:13 +0100 Subject: [PATCH] Added all the Hydrostatics to the console interface --- src/Mod/Ship/Ship.py | 3 +- src/Mod/Ship/shipAreasCurve/PlotAux.py | 4 +- src/Mod/Ship/shipAreasCurve/Preview.py | 6 +- src/Mod/Ship/shipAreasCurve/TaskPanel.py | 111 ++-- src/Mod/Ship/shipGZ/Tools.py | 8 +- src/Mod/Ship/shipHydrostatics/PlotAux.py | 57 +- src/Mod/Ship/shipHydrostatics/TaskPanel.py | 11 +- src/Mod/Ship/shipHydrostatics/Tools.py | 613 ++++++++++----------- 8 files changed, 371 insertions(+), 442 deletions(-) diff --git a/src/Mod/Ship/Ship.py b/src/Mod/Ship/Ship.py index e0a3aefe5..bea30aae5 100644 --- a/src/Mod/Ship/Ship.py +++ b/src/Mod/Ship/Ship.py @@ -30,4 +30,5 @@ __doc__="The Ships module provide a set of tools to make some specific Naval" \ " Architecture computations" from shipCreateShip.Tools import createShip -from shipHydrostatics.Tools import areas \ No newline at end of file +from shipHydrostatics.Tools import areas, displacement, wettedArea, moment, + floatingArea, BMT, mainFrameCoeff \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/PlotAux.py b/src/Mod/Ship/shipAreasCurve/PlotAux.py index e83578e28..35c5df9a6 100644 --- a/src/Mod/Ship/shipAreasCurve/PlotAux.py +++ b/src/Mod/Ship/shipAreasCurve/PlotAux.py @@ -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, diff --git a/src/Mod/Ship/shipAreasCurve/Preview.py b/src/Mod/Ship/shipAreasCurve/Preview.py index 5a13718b8..44fbd6cf9 100644 --- a/src/Mod/Ship/shipAreasCurve/Preview.py +++ b/src/Mod/Ship/shipAreasCurve/Preview.py @@ -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 \ No newline at end of file diff --git a/src/Mod/Ship/shipAreasCurve/TaskPanel.py b/src/Mod/Ship/shipAreasCurve/TaskPanel.py index d3308ae7a..c32db44f5 100644 --- a/src/Mod/Ship/shipAreasCurve/TaskPanel.py +++ b/src/Mod/Ship/shipAreasCurve/TaskPanel.py @@ -55,12 +55,11 @@ class TaskPanel: trim = Units.parseQuantity(Locale.fromString(form.trim.text())) num = form.num.value() - data = Hydrostatics.displacement(self.ship, - draft.getValueAs("m").Value, - 0.0, - trim.getValueAs("deg").Value) - disp = data[0] - xcb = data[1].x + disp, B, _ = Hydrostatics.displacement(self.ship, + draft, + Units.parseQuantity("0 deg"), + trim) + xcb = Units.Quantity(B.x, Units.Length) data = Hydrostatics.areas(self.ship, num, draft=draft, @@ -245,24 +244,11 @@ class TaskPanel: None, QtGui.QApplication.UnicodeUTF8)) - def clampLength(self, widget, val_min, val_max, val): - if val >= val_min and val <= val_max: + def clampValue(self, widget, val_min, val_max, val): + if val_min <= val <= val_max: return val - input_format = USys.getLengthFormat() val = min(val_max, max(val_min, val)) - qty = Units.Quantity('{} m'.format(val)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) - return val - - def clampAngle(self, widget, val_min, val_max, val): - if val >= val_min and val <= val_max: - return val - input_format = USys.getAngleFormat() - val = min(val_max, max(val_min, val)) - qty = Units.Quantity('{} deg'.format(val)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + widget.setText(val.UserString) return val def onData(self, value): @@ -279,32 +265,24 @@ class TaskPanel: # Get the values (or fix them in bad setting case) try: - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) except: - draft = self.ship.Draft.getValueAs(USys.getLengthUnits()).Value - input_format = USys.getLengthFormat() - qty = Units.Quantity('{} m'.format(draft)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + draft = self.ship.Draft + form.draft.setText(draft.UserString) try: - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) except: - trim = 0.0 - input_format = USys.getAngleFormat() - qty = Units.Quantity('{} deg'.format(trim)) - widget.setText(Locale.toString(input_format.format( - qty.getValueAs(USys.getLengthUnits()).Value))) + trim = Units.parseQuantity("0 deg") + form.trim.setText(trim.UserString) bbox = self.ship.Shape.BoundBox - draft_min = bbox.ZMin / Units.Metre.Value - draft_max = bbox.ZMax / Units.Metre.Value - draft = self.clampLength(form.draft, draft_min, draft_max, draft) + draft_min = Units.Quantity(bbox.ZMin, Units.Length) + draft_max = Units.Quantity(bbox.ZMax, Units.Length) + draft = self.clampValue(form.draft, draft_min, draft_max, draft) - trim_min = -180.0 - trim_max = 180.0 - trim = self.clampAngle(form.trim, trim_min, trim_max, trim) + trim_min = Units.parseQuantity("-180 deg") + trim_max = Units.parseQuantity("180 deg") + trim = self.clampValue(form.trim, trim_min, trim_max, trim) self.onUpdate() self.preview.update(draft, trim, self.ship) @@ -319,40 +297,39 @@ class TaskPanel: form.trim = self.widget(QtGui.QLineEdit, "Trim") form.output = self.widget(QtGui.QTextEdit, "OutputData") - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) # Calculate the drafts at each perpendicular - angle = math.radians(trim) + angle = trim.getValueAs("rad").Value L = self.ship.Length.getValueAs('m').Value B = self.ship.Breadth.getValueAs('m').Value - draftAP = draft + 0.5 * L * math.tan(angle) + draftAP = draft + 0.5 * self.ship.Length * math.tan(angle) if draftAP < 0.0: draftAP = 0.0 - draftFP = draft - 0.5 * L * math.tan(angle) + draftFP = draft - 0.5 * self.ship.Length * math.tan(angle) if draftFP < 0.0: draftFP = 0.0 # Calculate the involved hydrostatics - data = Hydrostatics.displacement(self.ship, - draft, - 0.0, - trim) + disp, B, _ = Hydrostatics.displacement(self.ship, + draft, + Units.parseQuantity("0 deg"), + trim) + xcb = Units.Quantity(B.x, Units.Length) # Setup the html string - string = 'L = {0} [m]
'.format(L) - string = string + 'B = {0} [m]
'.format(B) - string = string + 'T = {0} [m]
'.format(draft) - string = string + 'Trim = {0} [degrees]
'.format(trim) - string = string + 'TAP = {0} [m]
'.format(draftAP) - string = string + 'TFP = {0} [m]
'.format(draftFP) + string = u'L = {0}
'.format(self.ship.Length.UserString) + string += u'B = {0}
'.format(self.ship.Breadth.UserString) + string += u'T = {0}
'.format(draft.UserString) + string += u'Trim = {0}
'.format(trim.UserString) + string += u'TAP = {0}
'.format(draftAP.UserString) + string += u'TFP = {0}
'.format(draftFP.UserString) dispText = QtGui.QApplication.translate( "ship_areas", 'Displacement', None, QtGui.QApplication.UnicodeUTF8) - string = string + dispText + ' = {0} [ton]
'.format(data[0]) - string = string + 'XCB = {0} [m]'.format(data[1].x) + string += dispText + u' = {0}
'.format(disp.UserString) + string += u'XCB = {0}'.format(xcb.UserString) form.output.setHtml(string) def save(self): @@ -363,10 +340,8 @@ class TaskPanel: form.trim = self.widget(QtGui.QLineEdit, "Trim") form.num = self.widget(QtGui.QSpinBox, "Num") - draft = Units.Quantity(Locale.fromString( - form.draft.text())).getValueAs('m').Value - trim = Units.Quantity(Locale.fromString( - form.trim.text())).getValueAs('deg').Value + draft = Units.parseQuantity(Locale.fromString(form.draft.text())) + trim = Units.parseQuantity(Locale.fromString(form.trim.text())) num = form.num.value() props = self.ship.PropertiesList @@ -385,7 +360,7 @@ class TaskPanel: "AreaCurveDraft", "Ship", tooltip) - self.ship.AreaCurveDraft = '{} m'.format(draft) + self.ship.AreaCurveDraft = draft try: props.index("AreaCurveTrim") except ValueError: @@ -401,7 +376,7 @@ class TaskPanel: "AreaCurveTrim", "Ship", tooltip) - self.ship.AreaCurveTrim = '{} deg'.format(trim) + self.ship.AreaCurveTrim = trim try: props.index("AreaCurveNum") except ValueError: @@ -426,4 +401,4 @@ def createTask(): if panel.setupUi(): Gui.Control.closeDialog(panel) return None - return panel + return panel \ No newline at end of file diff --git a/src/Mod/Ship/shipGZ/Tools.py b/src/Mod/Ship/shipGZ/Tools.py index 6c91a54d4..a090b1ed3 100644 --- a/src/Mod/Ship/shipGZ/Tools.py +++ b/src/Mod/Ship/shipGZ/Tools.py @@ -121,8 +121,12 @@ def solve_point(W, COG, TW, VOLS, ship, tanks, roll, var_trim=True): for i in range(MAX_EQUILIBRIUM_ITERS): # Get the displacement, and the bouyance application point - disp, B, Cb = Hydrostatics.displacement(ship, draft, roll, trim) - disp *= 1000.0 * G + disp, B, _ = Hydrostatics.displacement(ship, + draft * Units.Metre, + roll * Units.Degree, + trim * Units.Degree) + disp = disp.getValueAs("kg").Value * G + B.multiply(1.0 / Units.Metre.Value) # Add the tanks effect on the center of gravity cog = Vector(COG.x * W, COG.y * W, COG.z * W) for i,t in enumerate(tanks): diff --git a/src/Mod/Ship/shipHydrostatics/PlotAux.py b/src/Mod/Ship/shipHydrostatics/PlotAux.py index 532d16f30..51702c1c4 100644 --- a/src/Mod/Ship/shipHydrostatics/PlotAux.py +++ b/src/Mod/Ship/shipHydrostatics/PlotAux.py @@ -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() \ No newline at end of file diff --git a/src/Mod/Ship/shipHydrostatics/TaskPanel.py b/src/Mod/Ship/shipHydrostatics/TaskPanel.py index f5c9d8107..c91f40912 100644 --- a/src/Mod/Ship/shipHydrostatics/TaskPanel.py +++ b/src/Mod/Ship/shipHydrostatics/TaskPanel.py @@ -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", diff --git a/src/Mod/Ship/shipHydrostatics/Tools.py b/src/Mod/Ship/shipHydrostatics/Tools.py index 6d729e50f..7c06842d0 100644 --- a/src/Mod/Ship/shipHydrostatics/Tools.py +++ b/src/Mod/Ship/shipHydrostatics/Tools.py @@ -34,7 +34,7 @@ from shipUtils import Math import shipUtils.Units as USys -DENS = 1.025 # [tons/m3], salt water +DENS = Units.parseQuantity("1025 kg/m^3") # Salt water COMMON_BOOLEAN_ITERATIONS = 10 @@ -49,9 +49,11 @@ def placeShipShape(shape, draft, roll, trim): roll -- Roll angle trim -- Trim angle - Returned value: - The same transformed input shape. Just for debugging purposes, you can - discard it. + Returned values: + shape -- The same transformed input shape. Just for debugging purposes, you + can discard it. + base_z -- The new base z coordinate (after applying the roll angle). Useful + if you want to revert back the transformation """ # Roll the ship. In order to can deal with large roll angles, we are # proceeding as follows: @@ -66,16 +68,21 @@ def placeShipShape(shape, draft, roll, trim): shape.translate(Vector(draft * math.sin(math.radians(trim)), 0.0, 0.0)) shape.translate(Vector(0.0, 0.0, -draft)) - return shape + return shape, base_z -def getUnderwaterSide(shape): +def getUnderwaterSide(shape, force=True): """Get the underwater shape, simply cropping the provided shape by the z=0 free surface plane. Position arguments: shape -- Solid shape to be cropped + Keyword arguments: + force -- True if in case the common boolean operation fails, i.e. returns + no solids, the tool should retry it slightly moving the free surface. False + otherwise. (True by default) + Returned value: Cropped shape. It is not modifying the input shape """ @@ -109,7 +116,7 @@ def getUnderwaterSide(shape): "UnderwaterSideHelper") common.Shapes = [orig, box] App.ActiveDocument.recompute() - if len(common.Shape.Solids) == 0: + if force and len(common.Shape.Solids) == 0: # The common operation is failing, let's try moving a bit the free # surface msg = QtGui.QApplication.translate( @@ -139,7 +146,7 @@ def getUnderwaterSide(shape): def areas(ship, n, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): - """ Compute the ship transversal areas + """Compute the ship transversal areas Position arguments: ship -- Ship object (see createShip) @@ -161,11 +168,8 @@ def areas(ship, n, draft=None, if draft is None: draft = ship.Draft - # Manipulate a copy of the ship shape - shape = getUnderwaterSide(placeShipShape(ship.Shape.copy(), - draft, - roll, - trim)) + shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) # Sections distance computation bbox = shape.BoundBox @@ -191,6 +195,12 @@ def areas(ship, n, draft=None, try: f = Part.Face(shape.slice(Vector(1,0,0), x)) except Part.OCCError: + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Transversal area computation failed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') areas.append((Units.Quantity(x, Units.Length), Units.Quantity(0.0, Units.Area))) continue @@ -204,397 +214,328 @@ def areas(ship, n, draft=None, return areas -def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): - """ Compute the ship displacement. - @param ship Ship instance. - @param draft Ship draft. - @param roll Ship roll angle. - @param trim Ship trim angle. - @param yaw Ship yaw angle. Ussually you don't want to use this - value. - @return [disp, B, Cb], \n - - disp = Ship displacement [ton]. - - B = Bouyance center [m]. - - Cb = Block coefficient. - @note Bouyance center will returned as a FreeCAD.Vector instance. - @note Returned Bouyance center is in the non modified ship coordinates +def displacement(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship displacement + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned values: + disp -- The ship displacement (a density of the water of 1025 kg/m^3 is + assumed) + B -- Bouyance application point, i.e. Center of mass of the underwater side + Cb -- Block coefficient + + The Bouyance center is refered to the original ship position. """ - # We will take a duplicate of ship shape in order to conviniently - # manipulate it - shape = ship.Shape.copy() - _draft = draft * Units.Metre.Value - # Roll the ship. In order to can deal with large roll angles, we are - # proceeding as follows: - # 1.- Applying the roll with respect the base line - # 2.- Recentering the ship in the y direction - # 3.- Readjusting the base line - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) - base_z = shape.BoundBox.ZMin - shape.translate(Vector(0.0, - _draft * math.sin(math.radians(roll)), - -base_z)) - # Trim and yaw the ship. In this case we only need to correct the x - # direction - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.translate(Vector(_draft * math.sin(math.radians(trim)), 0.0, 0.0)) - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) - shape.translate(Vector(0.0, 0.0, -_draft)) - Part.show(shape) - ship_shape = App.ActiveDocument.Objects[-1] + if draft is None: + draft = ship.Draft - bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax - zmin = bbox.ZMin - zmax = bbox.ZMax - # Create the "sea" box to intersect the ship - L = xmax - xmin - B = ymax - ymin - H = zmax - zmin - - box = App.ActiveDocument.addObject("Part::Box","Box") - length_format = USys.getLengthFormat() - box.Placement = Placement(Vector(xmin - L, ymin - B, zmin - H), - Rotation(App.Vector(0,0,1),0)) - box.Length = length_format.format(3.0 * L) - box.Width = length_format.format(3.0 * B) - box.Height = length_format.format(- zmin + H) - - App.ActiveDocument.recompute() - common = App.activeDocument().addObject("Part::MultiCommon", - "DisplacementHelper") - common.Shapes = [ship_shape, box] - App.ActiveDocument.recompute() - if len(common.Shape.Solids) == 0: - # The common operation is failing, let's try moving a bit the free - # surface - msg = QtGui.QApplication.translate( - "ship_console", - "Boolean operation failed. The tool is retrying that slightly" - " moving the free surface position", - None, - QtGui.QApplication.UnicodeUTF8) - App.Console.PrintWarning(msg + '\n') - random_bounds = 0.01 * H - i = 0 - while len(common.Shape.Solids) == 0 and i < COMMON_BOOLEAN_ITERATIONS: - i += 1 - box.Height = length_format.format( - - zmin + H + random.uniform(-random_bounds, random_bounds)) - App.ActiveDocument.recompute() + shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() - if len(common.Shape.Solids) > 0: - for solid in common.Shape.Solids: - vol += solid.Volume / Units.Metre.Value**3 + if len(shape.Solids) > 0: + for solid in shape.Solids: + vol += solid.Volume sCoG = solid.CenterOfMass - cog.x = cog.x + sCoG.x * solid.Volume / Units.Metre.Value**4 - cog.y = cog.y + sCoG.y * solid.Volume / Units.Metre.Value**4 - cog.z = cog.z + sCoG.z * solid.Volume / Units.Metre.Value**4 + cog.x = cog.x + sCoG.x * solid.Volume + cog.y = cog.y + sCoG.y * solid.Volume + cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol - Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3 - App.ActiveDocument.removeObject(common.Name) - App.ActiveDocument.removeObject(ship_shape.Name) - App.ActiveDocument.removeObject(box.Name) - App.ActiveDocument.recompute() + bbox = shape.BoundBox + Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) - # Undo the transformations + # Undo the transformations on the bouyance point B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) - m.rotateZ(-math.radians(yaw)) m.move(Vector(-draft * math.sin(math.radians(trim)), 0.0, 0.0)) m.rotateY(math.radians(trim)) m.move(Vector(0.0, -draft * math.sin(math.radians(roll)), - base_z / Units.Metre.Value)) + base_z)) m.rotateX(-math.radians(roll)) B.transform(m) - # Return the computed data - return [DENS*vol, Vector(B.X, B.Y, B.Z), vol/Vol] - - -def wettedArea(shape, draft, trim): - """ Calculate wetted ship area. - @param shape Ship external faces instance. - @param draft Draft. - @param trim Trim in degrees. - @return Wetted ship area. - """ - area = 0.0 - nObjects = 0 - - shape = shape.copy() - _draft = draft * Units.Metre.Value - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.translate(Vector(0.0, 0.0, -_draft)) - - bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax - - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: - box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return 0.0 - - for f in shape.Faces: - try: - common = box.common(f) - except Part.OCCError: - continue - area = area + common.Area - return area / Units.Metre.Value**2 + cb = vol / Vol + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null volume found during the displacement" + " computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cb = 0.0 -def moment(ship, draft, trim, disp, xcb): - """ Calculate triming 1cm ship moment. - @param ship Selected ship instance - @param draft Draft. - @param trim Trim in degrees. - @param disp Displacement at selected draft and trim. - @param xcb Bouyance center at selected draft and trim. - @return Moment to trim ship 1cm (ton m). - @note Moment is positive when produce positive trim. + # Return the computed data + return (DENS * Units.Quantity(vol, Units.Volume), + Vector(B.X, B.Y, B.Z), + cb) + + +def wettedArea(shape, draft, roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship wetted area + + Position arguments: + shape -- External faces of the ship hull + draft -- Ship draft + + Keyword arguments: + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + The wetted area, i.e. The underwater side area """ + shape, _ = placeShipShape(shape.copy(), draft, roll, trim) + shape = getUnderwaterSide(shape, force=False) + + area = 0.0 + for f in shape.Faces: + area = area + f.Area + return Units.Quantity(area, Units.Area) + + +def moment(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the moment required to trim the ship 1cm + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + Moment required to trim the ship 1cm. Such moment is positive if it cause a + positive trim angle. The moment is expressed as a mass by a distance, not as + a force by a distance + """ + disp_orig, B_orig, _ = displacement(ship, draft, roll, trim) + xcb_orig = Units.Quantity(B_orig.x, Units.Length) + factor = 10.0 - angle = factor * math.degrees(math.atan2( - 0.01, - 0.5 * ship.Length.getValueAs('m').Value)) - newTrim = trim + angle - data = displacement(ship, draft, 0.0, newTrim, 0.0) - mom0 = -disp * xcb - mom1 = -data[0] * data[1].x + x = 0.5 * ship.Length.getValueAs('cm').Value + y = 1.0 + angle = math.atan2(y, x) * Units.Radian + trim_new = trim + factor * angle + disp_new, B_new, _ = displacement(ship, draft, roll, trim_new) + xcb_new = Units.Quantity(B_new.x, Units.Length) + + mom0 = -disp_orig * xcb_orig + mom1 = -disp_new * xcb_new return (mom1 - mom0) / factor -def FloatingArea(ship, draft, trim): - """ Calculate ship floating area. - @param ship Selected ship instance - @param draft Draft. - @param trim Trim in degrees. - @return Ship floating area, and floating coefficient. - """ - area = 0.0 - cf = 0.0 - maxX = 0.0 - minX = 0.0 - maxY = 0.0 - minY = 0.0 +def floatingArea(ship, draft=None, + roll=Units.parseQuantity("0 deg"), + trim=Units.parseQuantity("0 deg")): + """Compute the ship floating area - shape = ship.Shape.copy() - _draft = draft * Units.Metre.Value - shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) - shape.translate(Vector(0.0, 0.0, -_draft)) + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + roll -- Roll angle (0 degrees by default) + trim -- Trim angle (0 degrees by default) + + Returned values: + area -- Ship floating area + cf -- Floating area coefficient + """ + if draft is None: + draft = ship.Draft + + # We wanna intersect the whole ship with the free surface, so in this case + # we must not use the underwater side (or the tool will fail) + shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) + + try: + f = Part.Face(shape.slice(Vector(0,0,1), 0.0)) + area = Units.Quantity(f.Area, Units.Area) + except Part.OCCError: + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Floating area cannot be computed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + area = Units.Quantity(0.0, Units.Area) bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax - - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) + Area = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) try: - box = Part.makeBox(3.0 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return [area, cf] + cf = area.Value / Area + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null area found during the floating area" + " computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cf = 0.0 - maxX = bbox.XMin / Units.Metre.Value - minX = bbox.XMax / Units.Metre.Value - maxY = bbox.YMin / Units.Metre.Value - minY = bbox.YMax / Units.Metre.Value - for s in shape.Solids: - try: - common = box.common(s) - except Part.OCCError: - continue - if common.Volume == 0.0: - continue - # Recompute the object adding it to the scene. OpenCASCADE must be - # performing an internal tesellation doing that - try: - Part.show(common) - except (TypeError,Part.OCCError): - continue - # Divide the solid by faces and filter the well placed ones - faces = common.Faces - for f in faces: - faceBounds = f.BoundBox - # Orientation filter - if faceBounds.ZMax - faceBounds.ZMin > 0.00001: - continue - # Position filter - if abs(faceBounds.ZMax) > 0.00001: - continue - - area = area + f.Area / Units.Metre.Value**2 - maxX = max(maxX, faceBounds.XMax / Units.Metre.Value) - minX = min(minX, faceBounds.XMin / Units.Metre.Value) - maxY = max(maxY, faceBounds.YMax / Units.Metre.Value) - minY = min(minY, faceBounds.YMin / Units.Metre.Value) - App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name) - - dx = maxX - minX - dy = maxY - minY - if dx*dy > 0.0: - cf = area / (dx * dy) - return [area, cf] + return area, cf -def BMT(ship, draft, trim=0.0): - """ Calculate ship Bouyance center transversal distance. - @param ship Ship instance. - @param draft Ship draft. - @param trim Ship trim angle. - @return BM Bouyance to metacenter height [m]. +def BMT(ship, draft=None, trim=Units.parseQuantity("0 deg")): + """Calculate "ship Bouyance center" - "transversal metacenter" radius + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + trim -- Trim angle (0 degrees by default) + + Returned value: + BMT radius """ + if draft is None: + draft = ship.Draft + + roll = Units.parseQuantity("0 deg") + _, B0, _ = displacement(ship, draft, roll, trim) + + nRoll = 2 - maxRoll = 7.0 - B0 = displacement(ship, draft, 0.0, trim, 0.0)[1] + maxRoll = Units.parseQuantity("7 deg") + BM = 0.0 for i in range(nRoll): - roll = (maxRoll / nRoll)*(i + 1) - B1 = displacement(ship, draft, roll, trim, 0.0)[1] + roll = (maxRoll / nRoll) * (i + 1) + _, B1, _ = displacement(ship, draft, roll, trim) # * M # / \ # / \ BM ==|> BM = (BB/2) / sin(alpha/2) # / \ # *-------* # BB - BB = [B1.y - B0.y, B1.z - B0.z] - BB = math.sqrt(BB[0] * BB[0] + BB[1] * BB[1]) - # nRoll is acting as the weight function - BM = BM + 0.5 * BB / math.sin(math.radians(0.5 * roll)) / nRoll - return BM + BB = B1 - B0 + BB.x = 0.0 + # nRoll is actually representing the weight function + BM += 0.5 * BB.Length / math.sin(math.radians(0.5 * roll)) / nRoll + return Units.Quantity(BM, Units.Length) -def mainFrameCoeff(ship, draft): - """ Calculate main frame coefficient. - @param ship Selected ship instance - @param draft Draft. - @return Main frame coefficient +def mainFrameCoeff(ship, draft=None): + """Compute the main frame coefficient + + Position arguments: + ship -- Ship object (see createShip) + + Keyword arguments: + draft -- Ship draft (Design ship draft by default) + + Returned value: + Ship main frame area coefficient """ - cm = 0.0 - maxY = 0.0 - minY = 0.0 + if draft is None: + draft = ship.Draft - shape = ship.Shape.copy() - shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) - x = 0.0 - area = 0.0 + shape, _ = placeShipShape(ship.Shape.copy(), draft, + Units.parseQuantity("0 deg"), + Units.parseQuantity("0 deg")) + shape = getUnderwaterSide(shape) + + try: + f = Part.Face(shape.slice(Vector(1,0,0), 0.0)) + area = f.Area + except Part.OCCError: + msg = QtGui.QApplication.translate( + "ship_console", + "Part.OCCError: Main frame area cannot be computed", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + area = 0.0 bbox = shape.BoundBox - xmin = bbox.XMin - xmax = bbox.XMax - ymin = bbox.YMin - ymax = bbox.YMax + Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin) - # Create the "sea" box - L = xmax - xmin - B = ymax - ymin - p = Vector(xmin - L, ymin - B, bbox.ZMin - 1.0) try: - box = Part.makeBox(1.5 * L, 3.0 * B, - bbox.ZMin + 1.0, p) - except Part.OCCError: - return cm + cm = area / Area + except ZeroDivisionError: + msg = QtGui.QApplication.translate( + "ship_console", + "ZeroDivisionError: Null area found during the main frame area" + " coefficient computation!", + None, + QtGui.QApplication.UnicodeUTF8) + App.Console.PrintError(msg + '\n') + cm = 0.0 - maxY = bbox.YMin / Units.Metre.Value - minY = bbox.YMax / Units.Metre.Value - for s in shape.Solids: - try: - common = box.common(s) - except Part.OCCError: - continue - if common.Volume == 0.0: - continue - # Recompute the object adding it to the scene. OpenCASCADE must be - # performing an internal tesellation doing that - try: - Part.show(common) - except (TypeError,Part.OCCError): - continue - # Divide the solid by faces and filter the well placed ones - faces = common.Faces - for f in faces: - faceBounds = f.BoundBox - # Orientation filter - if faceBounds.XMax - faceBounds.XMin > 0.00001: - continue - # Position filter - if abs(faceBounds.XMax - x) > 0.00001: - continue - - area = area + f.Area / Units.Metre.Value**2 - maxY = max(maxY, faceBounds.YMax / Units.Metre.Value) - minY = min(minY, faceBounds.YMin / Units.Metre.Value) - App.ActiveDocument.removeObject(App.ActiveDocument.Objects[-1].Name) - - dy = maxY - minY - if dy * draft > 0.0: - cm = area / (dy * draft) return cm class Point: - """ Hydrostatics point, that conatins: \n - draft Ship draft [m]. \n - trim Ship trim [deg]. \n - disp Ship displacement [ton]. \n - xcb Bouyance center X coordinate [m]. - wet Wetted ship area [m2]. - mom Triming 1cm ship moment [ton m]. - farea Floating area [m2]. - KBt Transversal KB height [m]. - BMt Transversal BM height [m]. - Cb Block coefficient. - Cf Floating coefficient. - Cm Main frame coefficient. - @note Moment is positive when produce positive trim. + """Hydrostatics point, that contains the following members: + + draft -- Ship draft + trim -- Ship trim + disp -- Ship displacement + xcb -- Bouyance center X coordinate + wet -- Wetted ship area + mom -- Triming 1cm ship moment + farea -- Floating area + KBt -- Transversal KB height + BMt -- Transversal BM height + Cb -- Block coefficient. + Cf -- Floating coefficient. + Cm -- Main frame coefficient. + + The moment to trim the ship 1 cm is positive when is resulting in a positive + trim angle. """ def __init__(self, ship, faces, draft, trim): - """ Use all hydrostatics tools to define a hydrostatics - point. - @param ship Selected ship instance - @param faces Ship external faces - @param draft Draft. - @param trim Trim in degrees. + """Compute all the hydrostatics. + + Position argument: + ship -- Ship instance + faces -- Ship external faces + draft -- Ship draft + trim -- Trim angle """ - # Hydrostatics computation - dispData = displacement(ship, draft, 0.0, trim, 0.0) + disp, B, cb = displacement(ship, draft=draft, trim=trim) if not faces: wet = 0.0 else: - wet = wettedArea(faces, draft, trim) - mom = moment(ship, draft, trim, dispData[0], dispData[1].x) - farea = FloatingArea(ship, draft, trim) - bm = BMT(ship, draft, trim) - cm = mainFrameCoeff(ship, draft) + wet = wettedArea(faces, draft=draft, trim=trim) + mom = moment(ship, draft=draft, trim=trim) + farea, cf = floatingArea(ship, draft=draft, trim=trim) + bm = BMT(ship, draft=draft, trim=trim) + cm = mainFrameCoeff(ship, draft=draft) # Store final data self.draft = draft self.trim = trim - self.disp = dispData[0] - self.xcb = dispData[1].x + self.disp = disp + self.xcb = Units.Quantity(B.x, Units.Length) self.wet = wet - self.farea = farea[0] + self.farea = farea self.mom = mom - self.KBt = dispData[1].z + self.KBt = Units.Quantity(B.z, Units.Length) self.BMt = bm - self.Cb = dispData[2] - self.Cf = farea[1] - self.Cm = cm + self.Cb = cb + self.Cf = cf + self.Cm = cm \ No newline at end of file