def onData(self, value): """ Method called when input data is changed. @param value Changed value. """ min_draft = Units.parseQuantity( Locale.fromString(self.form.min_draft.text())) max_draft = Units.parseQuantity( Locale.fromString(self.form.max_draft.text())) trim = Units.parseQuantity(Locale.fromString(self.form.trim.text())) if min_draft.Unit != Units.Length or \ max_draft.Unit != Units.Length or \ trim.Unit != Units.Angle: return # Clamp the values to the bounds bbox = self.ship.Shape.BoundBox draft_min = Units.Quantity(bbox.ZMin, Units.Length) draft_max = Units.Quantity(bbox.ZMax, Units.Length) min_draft = self.clampValue(self.form.min_draft, draft_min, draft_max, min_draft) max_draft = self.clampValue(self.form.max_draft, draft_min, draft_max, max_draft) # Check that the minimum value is lower than # the maximum one min_draft = self.clampValue(self.form.min_draft, draft_min, max_draft, min_draft) # Clamp the trim angle to sensible values trim = self.clampValue(self.form.trim, Units.parseQuantity("-90 deg"), Units.parseQuantity("90 deg"), trim)
def onData(self, value): """ Method called when the tool input data is touched. @param value Changed value. """ if not self.ship: return mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") # Get the values (or fix them in bad setting case) try: draft = Units.parseQuantity(Locale.fromString(form.draft.text())) except: draft = self.ship.Draft form.draft.setText(draft.UserString) try: trim = Units.parseQuantity(Locale.fromString(form.trim.text())) except: trim = Units.parseQuantity("0 deg") form.trim.setText(trim.UserString) bbox = self.ship.Shape.BoundBox 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 = 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)
def accept(self): if not self.ship: return False self.save() # Plot data mw = self.getMainWindow() 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.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, num, draft=draft, trim=trim) x = [] y = [] for i in range(0, len(data)): 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
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 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 curve", None) 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, vol)) return out
def getMoment(self, fp): """Compute the mass of the object, already taking into account the type of subentities. 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'), Units.parseQuantity('0 kg*m') ] for s in fp.Shape.Solids: mom = self._getVolumetricMoment(fp, s) for i in range(len(m)): m[i] = m[i] + mom[i] for f in fp.Shape.Faces: mom = self._getAreaMoment(fp, f) for i in range(len(m)): m[i] = m[i] + mom[i] for e in fp.Shape.Edges: mom = self._getLinearMoment(fp, e) for i in range(len(m)): m[i] = m[i] + mom[i] for v in fp.Shape.Vertexes: mom = self._getPuntualMoment(fp, v) for i in range(len(m)): m[i] = m[i] + mom[i] return m
def cog(lc, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the center of gravity in the upright ship reference system Position arguments: lc -- The load condition Keyword arguments: roll -- The roll angle trim -- The trim angle Returns: The center of gravity and the total weight (In Newtons) """ cog, w = GZ.weights_cog(get_lc_weights(lc)) # Add the tanks effect tw = Units.parseQuantity("0 kg") 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 t, dens, level in get_lc_tanks(lc): vol = t.Proxy.getVolume(t, level) t_weight = vol * dens * GZ.G t_cog = t.Proxy.getCoG(t, vol, roll, trim) mom_x += Units.Quantity(t_cog.x, Units.Length) * t_weight mom_y += Units.Quantity(t_cog.y, Units.Length) * t_weight mom_z += Units.Quantity(t_cog.z, Units.Length) * t_weight tw += vol * dens w += tw * GZ.G return Vector(mom_x / w, mom_y / w, mom_z / w), w
def moment(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the moment required to trim the ship 1cm Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned value: Moment required to trim the ship 1cm. Such moment is positive if it cause a positive trim angle. The moment is expressed as a mass by a distance, not as a force by a distance """ disp_orig, B_orig, _ = displacement(ship, draft, roll, trim) xcb_orig = Units.Quantity(B_orig.x, Units.Length) factor = 10.0 x = 0.5 * ship.Length.getValueAs('cm').Value y = 1.0 angle = math.atan2(y, x) * Units.Radian trim_new = trim + factor * angle disp_new, B_new, _ = displacement(ship, draft, roll, trim_new) xcb_new = Units.Quantity(B_new.x, Units.Length) mom0 = -disp_orig * xcb_orig mom1 = -disp_new * xcb_new return (mom1 - mom0) / factor
def 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 surface area """ shape, _ = placeShipShape(shape.copy(), draft, roll, trim) shape = getUnderwaterSide(shape, force=False) submerged_length = shape.BoundBox.ZLength area = 0.0 for f in shape.Faces: if f.BoundBox.ZLength < 0.01 * submerged_length and \ abs(f.BoundBox.ZMin) < 0.01 * submerged_length: # Discard the eventual intersections with the free surface continue area = area + f.Area return Units.Quantity(area, Units.Area)
def onUpdate(self): """ Method called when the data update is requested. """ if not self.ship: return draft = Units.parseQuantity(Locale.fromString(self.form.draft.text())) trim = Units.parseQuantity(Locale.fromString(self.form.trim.text())) # Calculate the drafts at each perpendicular angle = trim.getValueAs("rad").Value draftAP = draft + 0.5 * self.ship.Length * math.tan(angle) if draftAP < 0.0: draftAP = 0.0 draftFP = draft - 0.5 * self.ship.Length * math.tan(angle) if draftFP < 0.0: draftFP = 0.0 # Calculate the involved hydrostatics disp, B, _ = Hydrostatics.displacement(self.ship, draft, Units.parseQuantity("0 deg"), trim) xcb = Units.Quantity(B.x, Units.Length) # Setup the html string 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) string += u'Δ = {0}<BR>'.format(disp.UserString) string += u'XCB = {0}'.format(xcb.UserString) self.form.output_data.setHtml(string)
def save(self): """ Saves the data into ship instance. """ mw = self.getMainWindow() 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.parseQuantity(Locale.fromString(form.draft.text())) trim = Units.parseQuantity(Locale.fromString(form.trim.text())) num = form.num.value() props = self.ship.PropertiesList try: props.index("AreaCurveDraft") except ValueError: try: tooltip = str(QtGui.QApplication.translate( "ship_areas", "Areas curve tool draft selected [m]", None)) except: tooltip = "Areas curve tool draft selected [m]" self.ship.addProperty("App::PropertyLength", "AreaCurveDraft", "Ship", tooltip) self.ship.AreaCurveDraft = draft try: props.index("AreaCurveTrim") except ValueError: try: tooltip = str(QtGui.QApplication.translate( "ship_areas", "Areas curve tool trim selected [deg]", None)) except: tooltip = "Areas curve tool trim selected [deg]" self.ship.addProperty("App::PropertyAngle", "AreaCurveTrim", "Ship", tooltip) 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)) except: tooltip = "Areas curve tool number of points" self.ship.addProperty("App::PropertyInteger", "AreaCurveNum", "Ship", tooltip) self.ship.AreaCurveNum = num
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 weights_cog(weights): 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] W = W return Vector(mom_x / W, mom_y / W, mom_z / W), W * G
def accept(self): """Compute the RAOs and plot them""" if not self.lc: return False if self.running: return self.form.group_pbar.show() min_period = Units.parseQuantity(self.form.min_period.text()) max_period = Units.parseQuantity(self.form.max_period.text()) n_period = self.form.n_period.value() periods = [min_period + (max_period - min_period) * i / (n_period - 1) \ for i in range(n_period)] omegas = [2.0 * np.pi / T.getValueAs('s').Value for T in periods] # Generate the simulations sims = Tools.simulation(self.lc, self.ship, self.weights, self.tanks, self.mesh, omegas) # Start solving the problems self.loop = QtCore.QEventLoop() self.timer = QtCore.QTimer() self.timer.setSingleShot(True) QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.loop, QtCore.SLOT("quit()")) self.running = True n = len(periods) * len(Tools.DIRS) self.form.pbar.setMinimum(0) self.form.pbar.setMaximum(n) self.form.pbar.setValue(0) msg = QtGui.QApplication.translate("ship_console", "Computing RAOs", None) App.Console.PrintMessage(msg + '...\n') points = [] plts = {} for dof in Tools.DOFS: plts[dof] = PlotAux.Plot(dof, periods) for i, data in enumerate(Tools.solve_sim(sims, omegas)): App.Console.PrintMessage("\t{} / {}\n".format(i + 1, n)) self.form.pbar.setValue(i + 1) ii, jj, dataset = data for dof in Tools.DOFS: rao_complex = dataset.sel(radiating_dof=dof).data[0] plts[dof].rao[ii, jj + 1] = abs(rao_complex) plts[dof].phase[ii, jj + 1] = cmath.phase(rao_complex) plts[dof].update() self.timer.start(0.0) self.loop.exec_() if (not self.running): break return True
def floatingArea(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship floating area Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned values: area -- Ship floating area cf -- Floating area coefficient """ if draft is None: draft = ship.Draft # We want to intersect the whole ship with the free surface, so in this case # we must not use the underwater side (or the tool will fail) shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) try: f = Part.Face(shape.slice(Vector(0, 0, 1), 0.0)) area = Units.Quantity(f.Area, Units.Area) except Part.OCCError: msg = QtGui.QApplication.translate( "ship_console", "Part.OCCError: Floating area cannot be computed", None) App.Console.PrintError(msg + '\n') f = None area = Units.Quantity(0.0, Units.Area) bbox = shape.BoundBox Area = Units.Quantity((bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin), Units.Area) try: cf = (area / Area).Value except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null area found during the floating area" " computation!", None) App.Console.PrintError(msg + '\n') cf = 0.0 return area, cf, f
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 referred 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. """ if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: vol = 0.0 cog = Vector() 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 shape = self.getFluidShape(fp, vol, roll, trim) # Get the center of gravity vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog
def floatingArea(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship floating area Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned values: area -- Ship floating area cf -- Floating area coefficient """ if draft is None: draft = ship.Draft # We want to intersect the whole ship with the free surface, so in this case # we must not use the underwater side (or the tool will fail) shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) try: f = Part.Face(shape.slice(Vector(0,0,1), 0.0)) area = Units.Quantity(f.Area, Units.Area) except Part.OCCError: msg = QtGui.QApplication.translate( "ship_console", "Part.OCCError: Floating area cannot be computed", None) App.Console.PrintError(msg + '\n') area = Units.Quantity(0.0, Units.Area) bbox = shape.BoundBox Area = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) try: cf = area.Value / Area except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null area found during the floating area" " computation!", None) App.Console.PrintError(msg + '\n') cf = 0.0 return area, cf
def get_lc_tanks(lc): objs = [] i = 6 while True: cells = ['{}{}'.format(c, i) for c in ('C', 'D', 'E')] try: label = lc.get('C{}'.format(i)) dens = lc.get('D{}'.format(i)) level = lc.get('E{}'.format(i)) except ValueError: break i += 1 tanks = lc.Document.getObjectsByLabel(label) if len(tanks) != 1: msg = QtGui.QApplication.translate( "ship_console", "Several tanks are labelled '{}'!".format(label), None) App.Console.PrintError(msg + '\n') continue tank = tanks[0] try: if not tank.IsTank: continue except AttributeError: continue try: dens = float(dens) level = float(level) except ValueError: continue dens = Units.parseQuantity('{} kg / m^3'.format(dens)) objs.append((tank, dens, level)) return objs
def _getPuntualMass(self, fp, shape): """Compute the mass of a puntual element. Position arguments: fp -- Part::FeaturePython object affected. shape -- Vertex shape object. """ return Units.parseQuantity('{0} kg'.format(fp.Mass))
def mainFrameCoeff(ship, draft=None): """Compute the main frame coefficient Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) Returned value: Ship main frame area coefficient """ if draft is None: draft = ship.Draft shape, _ = placeShipShape(ship.Shape.copy(), draft, Units.parseQuantity("0 deg"), Units.parseQuantity("0 deg")) shape = getUnderwaterSide(shape) try: f = Part.Face(shape.slice(Vector(1,0,0), 0.0)) area = f.Area except Part.OCCError: msg = QtGui.QApplication.translate( "ship_console", "Part.OCCError: Main frame area cannot be computed", None) App.Console.PrintError(msg + '\n') area = 0.0 bbox = shape.BoundBox Area = (bbox.YMax - bbox.YMin) * (bbox.ZMax - bbox.ZMin) try: cm = area / Area except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null area found during the main frame area" " coefficient computation!", None) App.Console.PrintError(msg + '\n') cm = 0.0 return cm
def BMT(ship, fs, draft=None, trim=Units.parseQuantity("0 deg")): """Calculate "ship Bouyance center" - "transversal metacenter" radius Position arguments: ship -- Ship object (see createShip) fs -- The shape of the free surface. If None is passed, the BMT will be computed heuristically, i.e. the ship will be rotated a small angle, tracking the bouyance center. 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") disp, B0, _ = displacement(ship, draft, roll=roll, trim=trim) if fs is not None: vol = disp / DENS inertia_unit = (Units.Quantity(1, Units.Length)**4).Unit return Units.Quantity(fs.MatrixOfInertia.A11, inertia_unit) / vol nRoll = 2 maxRoll = Units.parseQuantity("7 deg") BM = 0.0 for i in range(nRoll): roll = (maxRoll / nRoll) * (i + 1) _, B1, _ = displacement(ship, draft, roll, trim) # * M # / \ # / \ BM ==|> BM = (BB/2) / sin(alpha/2) # / \ # *-------* # BB BB = B1 - B0 BB.x = 0.0 # nRoll is actually representing the weight function BM += 0.5 * BB.Length / math.sin(math.radians(0.5 * roll)) / nRoll return Units.Quantity(BM, Units.Length)
def _getVolumetricMass(self, fp, shape): """Compute the mass of a volumetric element. Position arguments: fp -- Part::FeaturePython object affected. shape -- Solid shape object. """ rho = Units.parseQuantity('{0} kg/m^3'.format(fp.Dens)) v = Units.Quantity(shape.Volume, Units.Volume) return rho * v
def onUpdate(self): """ Method called when the data update is requested. """ if not self.ship: return mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.draft = self.widget(QtGui.QLineEdit, "Draft") form.trim = self.widget(QtGui.QLineEdit, "Trim") form.output = self.widget(QtGui.QTextEdit, "OutputData") draft = Units.parseQuantity(Locale.fromString(form.draft.text())) trim = Units.parseQuantity(Locale.fromString(form.trim.text())) # Calculate the drafts at each perpendicular angle = trim.getValueAs("rad").Value L = self.ship.Length.getValueAs('m').Value B = self.ship.Breadth.getValueAs('m').Value draftAP = draft + 0.5 * self.ship.Length * math.tan(angle) if draftAP < 0.0: draftAP = 0.0 draftFP = draft - 0.5 * self.ship.Length * math.tan(angle) if draftFP < 0.0: draftFP = 0.0 # Calculate the involved hydrostatics disp, B, _ = Hydrostatics.displacement(self.ship, draft, Units.parseQuantity("0 deg"), trim) xcb = Units.Quantity(B.x, Units.Length) # Setup the html string 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) string += dispText + u' = {0}<BR>'.format(disp.UserString) string += u'XCB = {0}'.format(xcb.UserString) form.output.setHtml(string)
def _getAreaMass(self, fp, shape): """Compute the mass of an area element. Position arguments: fp -- Part::FeaturePython object affected. shape -- Face shape object. """ rho = Units.parseQuantity('{0} kg/m^2'.format(fp.AreaDens)) a = Units.Quantity(shape.Area, Units.Area) return rho * a
def _getLinearMass(self, fp, shape): """Compute the mass of a linear element. Position arguments: fp -- Part::FeaturePython object affected. shape -- Edge shape object. """ rho = Units.parseQuantity('{0} kg/m'.format(fp.LineDens)) l = Units.Quantity(shape.Length, Units.Length) return rho * l
def getFluidShape(self, fp, vol, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Return the tank fluid shape for the provided rotation angles. The returned shape is however not rotated at all Keyword arguments: fp -- Part::FeaturePython object affected. vol -- Volume of fluid. roll -- Ship roll angle. trim -- Ship trim angle. """ if vol <= 0.0: return None if vol >= fp.Shape.Volume: return fp.Shape.copy() # 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) m.rotateY(-trim) 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 += COMMON_BOOLEAN_RELAXATION[i] * error # Untransform the object to retrieve the original position fp.Placement = current_placement m = shape.Placement.toMatrix() m.rotateY(trim) m.rotateX(-roll) shape.Placement = Placement(m) return shape
def accept(self): if not self.tank: return False self.form.group_pbar.show() n = self.form.points.value() self.form.pbar.setMinimum(0) self.form.pbar.setMaximum(n - 1) self.form.pbar.setValue(0) # Start iterating self.loop = QtCore.QEventLoop() self.timer = QtCore.QTimer() self.timer.setSingleShot(True) QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.loop, QtCore.SLOT("quit()")) self.running = True # Get the hydrostatics msg = QtGui.QApplication.translate("ship_console", "Computing capacity curve", None) App.Console.PrintMessage(msg + '...\n') l = [0.0] z = [Units.parseQuantity("0 m")] v = [Units.parseQuantity("0 m^3")] plt = None for i in range(1, n): App.Console.PrintMessage("\t{} / {}\n".format(i, n - 1)) self.form.pbar.setValue(i) level = i / (n - 1) h, vol = Tools.compute_capacity(self.tank, level) l.append(100 * level) z.append(h) v.append(vol) if plt is None: plt = PlotAux.Plot(l, z, v) else: plt.update(l, z, v) self.timer.start(0.0) self.loop.exec_() if (not self.running): break return True
def onData(self, value): """ Method called when the tool input data is touched. @param value Changed value. """ draft = Units.parseQuantity(Locale.fromString(self.form.draft.text())) trim = Units.parseQuantity(Locale.fromString(self.form.trim.text())) if draft.Unit != Units.Length or trim.Unit != Units.Angle: return bbox = self.ship.Shape.BoundBox draft_min = Units.Quantity(bbox.ZMin, Units.Length) draft_max = Units.Quantity(bbox.ZMax, Units.Length) draft = self.clampValue(self.form.draft, draft_min, draft_max, draft) trim_min = Units.parseQuantity("-90 deg") trim_max = Units.parseQuantity("90 deg") trim = self.clampValue(self.form.trim, trim_min, trim_max, trim) self.onUpdate() self.preview.update(draft, trim, self.ship)
def TMC(ship, fs, 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) fs -- The shape of the free surface. If None is passed, the BML will be computed heuristically, i.e. the ship will be rotated a small angle, tracking the bouyance center. 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 """ if draft is None: draft = ship.Draft if fs is not None: bml, disp = BML(ship, fs, draft=draft, roll=roll, trim=trim) return disp * bml / ship.Length.getValueAs('cm').Value disp_orig, B_orig, _ = displacement(ship, draft, roll, trim) factor = 10.0 x = 0.5 * ship.Length.getValueAs('cm').Value y = 1.0 angle = math.atan2(y, x) * Units.Radian trim_new = trim + factor * angle disp_new, B_new, _ = displacement(ship, draft, roll, trim_new) mom0 = disp_orig * Units.Quantity(B_orig.x, Units.Length) mom1 = disp_new * Units.Quantity(B_new.x, Units.Length) return (mom1 - mom0) / factor
def accept(self): """Create the ship instance""" mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.ship = self.widget(QtGui.QComboBox, "Ship") form.weight = self.widget(QtGui.QLineEdit, "Weight") ship = self.ships[form.ship.currentIndex()] density = Units.parseQuantity(Locale.fromString(form.weight.text())) Tools.createWeight(self.shapes, ship, density) return True
def accept(self): """Create the ship instance""" mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.ship = self.widget(QtGui.QComboBox, "Ship") form.weight = self.widget(QtGui.QLineEdit, "Weight") ship = self.ships[form.ship.currentIndex()] density = Units.parseQuantity(Locale.fromString(form.weight.text())) Tools.createWeight(self.shapes, ship, density) return True
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 accept(self): if not self.ship: return False self.save() # Plot data draft = Units.parseQuantity(Locale.fromString(self.form.draft.text())) trim = Units.parseQuantity(Locale.fromString(self.form.trim.text())) num = self.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, num, draft=draft, trim=trim) x = [] y = [] for i in range(0, len(data)): 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
def BMT(ship, draft=None, trim=Units.parseQuantity("0 deg")): """Calculate "ship Bouyance center" - "transversal metacenter" radius Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) trim -- Trim angle (0 degrees by default) Returned value: BMT radius """ if draft is None: draft = ship.Draft roll = Units.parseQuantity("0 deg") _, B0, _ = displacement(ship, draft, roll, trim) nRoll = 2 maxRoll = Units.parseQuantity("7 deg") BM = 0.0 for i in range(nRoll): roll = (maxRoll / nRoll) * (i + 1) _, B1, _ = displacement(ship, draft, roll, trim) # * M # / \ # / \ BM ==|> BM = (BB/2) / sin(alpha/2) # / \ # *-------* # BB BB = B1 - B0 BB.x = 0.0 # nRoll is actually representing the weight function BM += 0.5 * BB.Length / math.sin(math.radians(0.5 * roll)) / nRoll return Units.Quantity(BM, Units.Length)
#* * #*************************************************************************** import math import FreeCAD as App import FreeCADGui as Gui from FreeCAD import Vector, Matrix, Placement import Part from FreeCAD 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
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) 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 accept(self): if not self.ship: return False if self.running: return self.save() mw = self.getMainWindow() form = mw.findChild(QtGui.QWidget, "TaskPanel") form.trim = self.widget(QtGui.QLineEdit, "Trim") form.minDraft = self.widget(QtGui.QLineEdit, "MinDraft") form.maxDraft = self.widget(QtGui.QLineEdit, "MaxDraft") form.nDraft = self.widget(QtGui.QSpinBox, "NDraft") 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 drafts = [draft] dDraft = (max_draft - min_draft) / (n_draft - 1) for i in range(1, n_draft): draft = draft + dDraft drafts.append(draft) # Get external faces self.loop = QtCore.QEventLoop() self.timer = QtCore.QTimer() self.timer.setSingleShot(True) QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.loop, QtCore.SLOT("quit()")) self.running = True faces = self.externalFaces(self.ship.Shape) if not self.running: return False if len(faces) == 0: msg = QtGui.QApplication.translate( "ship_console", "Failure detecting external faces from the ship object", None) App.Console.PrintError(msg + '\n') return False faces = Part.makeShell(faces) # Get the hydrostatics msg = QtGui.QApplication.translate( "ship_console", "Computing hydrostatics", None) App.Console.PrintMessage(msg + '...\n') points = [] for i in range(len(drafts)): App.Console.PrintMessage("\t{} / {}\n".format(i + 1, len(drafts))) draft = drafts[i] point = Tools.Point(self.ship, faces, draft, trim) points.append(point) self.timer.start(0.0) self.loop.exec_() if(not self.running): break PlotAux.Plot(self.ship, trim, points) return True
def displacement(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship displacement Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned values: disp -- The ship displacement (a density of the water of 1025 kg/m^3 is assumed) B -- Bouyance application point, i.e. Center of mass of the underwater side Cb -- Block coefficient The Bouyance center is referred to the original ship position. """ if draft is None: draft = ship.Draft shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol bbox = shape.BoundBox Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) # Undo the transformations on the bouyance point B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) m.move(Vector(-draft * math.sin(trim.getValueAs("rad")), 0.0, 0.0)) m.rotateY(trim.getValueAs("rad")) m.move(Vector(0.0, -draft * math.sin(roll.getValueAs("rad")), base_z)) m.rotateX(-roll.getValueAs("rad")) B.transform(m) try: cb = vol / Vol except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null volume found during the displacement" " computation!", None) App.Console.PrintError(msg + '\n') cb = 0.0 # Return the computed data return (DENS * Units.Quantity(vol, Units.Volume), Vector(B.X, B.Y, B.Z), cb)
def areas(ship, n, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship transversal areas Position arguments: ship -- Ship object (see createShip) n -- Number of points to compute Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned value: List of sections, each section contains 2 values, the x longitudinal coordinate, and the transversal area. If n < 2, an empty list will be returned. """ if n < 2: return [] if draft is None: draft = ship.Draft shape, _ = placeShipShape(ship.Shape.copy(), draft, roll, trim) shape = getUnderwaterSide(shape) # Sections distance computation bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax dx = (xmax - xmin) / (n - 1.0) # Since we are computing the sections in the total length (not in the # length between perpendiculars), we can grant that the starting and # ending sections have null area areas = [(Units.Quantity(xmin, Units.Length), Units.Quantity(0.0, Units.Area))] # And since we just need to compute areas we will create boxes with its # front face at the desired transversal area position, computing the # common solid part, dividing it by faces, and getting only the desired # ones. App.Console.PrintMessage("Computing transversal areas...\n") App.Console.PrintMessage("Some Inventor representation errors can be" " shown, please ignore them.\n") for i in range(1, n - 1): App.Console.PrintMessage("{0} / {1}\n".format(i, n - 2)) x = xmin + i * dx try: f = Part.Face(shape.slice(Vector(1,0,0), x)) except Part.OCCError: msg = QtGui.QApplication.translate( "ship_console", "Part.OCCError: Transversal area computation failed", None) App.Console.PrintError(msg + '\n') areas.append((Units.Quantity(x, Units.Length), Units.Quantity(0.0, Units.Area))) continue # It is a valid face, so we can add this area areas.append((Units.Quantity(x, Units.Length), Units.Quantity(f.Area, Units.Area))) # Last area is equal to zero (due to the total length usage) areas.append((Units.Quantity(xmax, Units.Length), Units.Quantity(0.0, Units.Area))) App.Console.PrintMessage("Done!\n") return areas
#*************************************************************************** import math import random from FreeCAD import Vector, Rotation, Matrix, Placement import Part from FreeCAD import Units import FreeCAD as App import FreeCADGui as Gui from PySide import QtGui, QtCore import Instance from shipUtils import Math import shipUtils.Units as USys DENS = Units.parseQuantity("1025 kg/m^3") # Salt water COMMON_BOOLEAN_ITERATIONS = 10 def placeShipShape(shape, draft, roll, trim): """Move the ship shape such that the free surface matches with the plane z=0. The transformation will be applied on the input shape, so copy it before calling this method if it should be preserved. Position arguments: shape -- Ship shape draft -- Ship draft roll -- Roll angle trim -- Trim angle Returned values:
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) App.Console.PrintError(msg + '\n'.format( lc.get('B1'))) else: msg = QtGui.QApplication.translate( "ship_console", "Ambiguous Ship label! ({} instances labeled as" "'{}' found)", None) 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) 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) 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) 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) 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) 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) 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)