def prepare_for_fd(s): """Prepares the model for frequency domain analysis. WARNING: this is a destructive method. Please run this on a copy of a scene. All wave-interaction nodes shall be at the origin of an axis system. That axis system shall not have a parent That axis systen shall have all dofs set to free Raises: ValueError if the scene can not be prepared. """ wis = s.nodes_of_type(node_class=WaveInteraction1) for w in wis: # checks assert isinstance(w.parent, Axis), ValueError( 'Parent of "{}" shall be an axis or derived'.format(w.name)) assert not np.any(w.parent.fixed), ValueError( 'Parent of "{}" shall have all its dofs set to free (not fixed)'. format(w.name)) # loads hydrodynamic data try: if w._hyddb is not None: loaddb = True else: loaddb = False except: loaddb = True if loaddb: w._hyddb = Hyddb1() w._hyddb.load_from(s.get_resource_path(w.path)) if np.all(w.offset == (0, 0, 0)): continue # create a new axis system at the global position and orientation of the node glob_position = w.parent.to_glob_position(w.offset) glob_rotation = w.parent.global_rotation name = s.available_name_like('autocreated_parent_for_{}'.format( w.name)) new_parent = s.new_axis(name, position=glob_position, rotation=glob_rotation, fixed=False) w.parent.change_parent_to(new_parent) w.parent.fixed = True w.parent = new_parent w.offset = (0, 0, 0)
def test_load_nc(): hyd = Hyddb1() hyd.load_from_capytaine(r"files/capytaine.nc") omega = 0.01 mass = hyd.amass(omega=omega) damping = hyd.damping(omega=omega) force = hyd.force(omega=omega, wave_direction=90)
def export_ofx_yml(s, filename): """Convert the scene to a orcaflex .yml file. Only compatible nodes are exported. Make the scene orcaflex compatible before exporting. Visuals of .obj type are supported by orcaflex. These are copied to the same folder as the .yml file Args: s : Scene filename : file to write to (.yml) """ filename = Path(filename) # convert to path # filename.parent : folder s.sort_nodes_by_dependency() buoys = [] winches = [] constraints = [] vessel_types = [] vessels = [] Shapes = [] line_types = [] lines = [] for n in s._nodes: if isinstance(n, BallastSystem): # calculate the inertia ixx = 0 iyy = 0 izz = 0 mass = 0 for tank in n._tanks: mass += tank.inertia inertia = tank.inertia ixx += inertia * (tank.position[1] ** 2 + tank.position[2] ** 2) iyy += inertia * (tank.position[0] ** 2 + tank.position[2] ** 2) izz += inertia * (tank.position[0] ** 2 + tank.position[1] ** 2) ixx = min(ixx, OFX_ZERO_MASS) iyy = min(iyy, OFX_ZERO_MASS) izz = min(izz, OFX_ZERO_MASS) I = [ixx, iyy, izz] pos = [*n.position] cog = [float(i) for i in n.cog] b = {'Name': n.name, 'Connection': n.parent.name, 'InitialPosition': pos, 'Mass': mass, 'Volume': 0, 'MomentsOfInertia': I, 'CentreOfMass': cog } buoys.append(b) if isinstance(n, (RigidBody, Axis)): if isinstance(n, RigidBody): mass = max(n.mass, OFX_ZERO_MASS) I = (mass * n.inertia_radii ** 2).tolist() cog = [*n.cog] elif isinstance(n, Axis): mass = OFX_ZERO_MASS I = [OFX_ZERO_MASS, OFX_ZERO_MASS, OFX_ZERO_MASS] cog = [0, 0, 0] # check the connection pos = [*n.position] rot = [*rotation_to_attitude(n.rotation)] if not any(n.fixed): connection = 'Free' elif np.all(n.fixed): if n.parent is None: connection = 'Fixed' else: connection = n.parent.name else: # Partially fixed - create constraint cname = n.name + ' [fixes]' if n.parent is None: connection = 'Fixed' else: connection = n.parent.name fixes = [] for f in n.fixed: if f: fixes.append('No') else: fixes.append('Yes') c = {'Name': cname, 'Connection': connection, 'DOFFree': fixes, 'InitialPosition': pos, 'InitialAttitude': rot, } constraints.append(c) # set the props for the 6d buoy connection = cname pos = [0,0,0] rot = [0,0,0] b = {'Name': n.name, 'Connection': connection, 'InitialPosition': pos, 'InitialAttitude': rot, 'Mass': mass, 'Volume': 0, 'MomentsOfInertia': I, 'CentreOfMass': cog } # create the basic buoy, but do not add it yet as some of the properties may be # overwritten by the vessel that we may create now ## Vessels -------------- # # If one of the children of this Axis is a HydSpring (linear hydrostatics node), then # 1. Look for a waveinteraction1 node as well # # if both are found then # 1. create a vessel type # 2. create a vessel without any mass # 3. place the buoy that we just created on the vessel children = s.nodes_with_parent(n) hyd_spring = None hyd_db = None for child in children: cn = s[child] if isinstance(cn, HydSpring): hyd_spring = cn if isinstance(cn, WaveInteraction1): hyd_db = cn if hyd_spring is not None: # create a vessel type vt = {'Name': n.name + hyd_spring.name, 'Length':1, # conventions 'WavesReferredToBy':'frequency (rad/s)', 'RAOPhaseConvention':'lags', 'RAOPhaseUnitsConvention':'radians' } # Stiffness, Added mass, damping # # The Reference-origin defines where all of these forces are applied # so it needs to be the origin of the hydrodynamic data, if we have any ref_origin = (0.,0.,0.) if hyd_db is not None: ref_origin = hyd_db.offset # calculate the stiffness matrix relative to this point k = np.zeros((3,3)) # Heave and heave coupling k[0,0] = hyd_spring.kHeave k[0,1] = -hyd_spring.kHeave * (hyd_spring.cob[1] + hyd_spring.COFY - ref_origin[1]) # heave-roll k[0,2] = -hyd_spring.kHeave * (hyd_spring.cob[0] + hyd_spring.COFX - ref_origin[0]) # heave-pitch k[1,0] = k[0,1] k[2,0] = k[0,2] # BML and BMT k[1,1] = hyd_spring.displacement_kN * (hyd_spring.BMT - ref_origin[2] + hyd_spring.cob[2]) # g * rho * disp * BMt k[2, 2] = hyd_spring.displacement_kN * (hyd_spring.BML - ref_origin[2] + hyd_spring.cob[2]) # g * rho * disp * BMt d = {'Name':'Draught1', 'Mass':1e-6, 'MomentOfInertiaTensorX': [0.001,0,0], 'MomentOfInertiaTensorY': [0,0.001,0], 'MomentOfInertiaTensorZ': [0,0,0.001], 'CentreOfGravityX': 0, 'CentreOfGravityY': 0, 'CentreOfGravityZ': 0, 'CentreOfBuoyancyX' : hyd_spring.cob[0], 'CentreOfBuoyancyY' : hyd_spring.cob[1], 'CentreOfBuoyancyZ' : hyd_spring.cob[2], # Stiffness, added mass, damping 'StiffnessInertiaDampingRefOriginx':ref_origin[0], 'StiffnessInertiaDampingRefOriginy':ref_origin[1], 'StiffnessInertiaDampingRefOriginz':ref_origin[2], 'HydrostaticReferenceOriginDatumPositionz':n.to_glob_position(ref_origin)[2], 'HydrostaticReferenceOriginDatumOrientationx':0, 'HydrostaticReferenceOriginDatumOrientationy':0, 'DisplacedVolume': hyd_spring.displacement_kN / (RHO * G), 'HydrostaticStiffnessz' : k[:,0].tolist(), 'HydrostaticStiffnessRx':k[:,1].tolist(), 'HydrostaticStiffnessRy': k[:,2].tolist(), # other damping settings # 'OtherDampingCalculatedFrom', # Add once version > 10.2d 'OtherDampingOriginx':ref_origin[0], 'OtherDampingOriginy':ref_origin[1], 'OtherDampingOriginz':ref_origin[2], 'OtherDampingLinearCoeffx':0, 'OtherDampingLinearCoeffy':0, 'OtherDampingLinearCoeffz':0, 'OtherDampingLinearCoeffRx':0, 'OtherDampingLinearCoeffRy':0, 'OtherDampingLinearCoeffRz':0, 'OtherDampingQuadraticCoeffx':0, 'OtherDampingQuadraticCoeffy':0, 'OtherDampingQuadraticCoeffz':0, 'OtherDampingQuadraticCoeffRx':0, 'OtherDampingQuadraticCoeffRy':0, 'OtherDampingQuadraticCoeffRz':0 } # Export hydrodynamics, if any if hyd_db is not None: # Export # Wave-forces (Force RAOs) # Damping # Added mass LoadRAOs = {'RAOOriginX': ref_origin[0], # TODO: These values do not seem to be loaded into OFX 'RAOOriginY': ref_origin[1], 'RAOOriginZ': ref_origin[2]} # load the database from mafredo.hyddb1 import Hyddb1 database = s.get_resource_path(hyd_db.path) db = Hyddb1() db.load_from(database) # get the available headings a_headings = db.force_rao(0)._data['wave_direction'].values a_frequencies = db.frequencies rao_mode = [] for i in range(6): rao_mode.append(db.force_rao(i)) RAOs = [] for heading in a_headings: rao = {'RAODirection':float(heading)} RAOPeriodOrFrequency = [] RAOSurgeAmp = [] RAOSurgePhase = [] RAOSwayAmp = [] RAOSwayPhase = [] RAOHeaveAmp = [] RAOHeavePhase = [] RAORollAmp = [] RAORollPhase = [] RAOPitchAmp = [] RAOPitchPhase = [] RAOYawAmp = [] RAOYawPhase = [] for frequency in a_frequencies: RAOPeriodOrFrequency.append(float(frequency)) r = rao_mode[0].get_value(wave_direction=heading, omega = frequency) RAOSurgeAmp.append(float(np.abs(r))) RAOSurgePhase.append(float(np.angle(r))) r = rao_mode[1].get_value(wave_direction=heading, omega=frequency) RAOSwayAmp.append(float(np.abs(r))) RAOSwayPhase.append(float(np.angle(r))) r = rao_mode[2].get_value(wave_direction=heading, omega=frequency) RAOHeaveAmp.append(float(np.abs(r))) RAOHeavePhase.append(float(np.angle(r))) r = rao_mode[3].get_value(wave_direction=heading, omega=frequency) RAORollAmp.append(float(np.abs(r))) RAORollPhase.append(float(np.angle(r))) r = rao_mode[4].get_value(wave_direction=heading, omega=frequency) RAOPitchAmp.append(float(np.abs(r))) RAOPitchPhase.append(float(np.angle(r))) r = rao_mode[5].get_value(wave_direction=heading, omega=frequency) RAOYawAmp.append(float(np.abs(r))) RAOYawPhase.append(float(np.angle(r))) rao['RAOPeriodOrFrequency'] = RAOPeriodOrFrequency rao['RAOSurgeAmp'] = RAOSurgeAmp rao['RAOSurgePhase'] = RAOSurgePhase rao['RAOSwayAmp'] = RAOSwayAmp rao['RAOSwayPhase'] = RAOSwayPhase rao['RAOHeaveAmp'] = RAOHeaveAmp rao['RAOHeavePhase'] = RAOHeavePhase rao['RAORollAmp'] = RAORollAmp rao['RAORollPhase'] = RAORollPhase rao['RAOPitchAmp'] = RAOPitchAmp rao['RAOPitchPhase'] = RAOPitchPhase rao['RAOYawAmp'] = RAOYawAmp rao['RAOYawPhase'] = RAOYawPhase RAOs.append(rao) LoadRAOs['RAOs'] = RAOs d['LoadRAOs'] = LoadRAOs # Added mass and Damping FrequencyDependentAddedMassAndDamping = [] for frequency in a_frequencies: entry = {'AMDPeriodOrFrequency': float(frequency)} B = db.damping(frequency) A = db.amass(frequency) # Make symmetric (else Orcaflex will not read the yml) def make_orcaflex_happy(mat): mat = 0.5 * mat + 0.5 * mat.transpose() R = np.zeros((6, 6)) R[0, 0] = mat[0, 0] R[1, 1] = mat[1, 1] R[2, 2] = mat[2, 2] R[3, 3] = mat[3, 3] R[4, 4] = mat[4, 4] R[5, 5] = mat[5, 5] # oA[0,2] = 7 # error R[2, 0] = mat[2, 0] R[0, 4] = mat[0, 4] R[4, 0] = mat[0, 4] R[1, 3] = mat[1, 3] # need both R[3, 1] = mat[1, 3] R[1, 5] = mat[1, 5] # need both R[5, 1] = mat[1, 5] R[5, 3] = mat[3, 5] return R oA = make_orcaflex_happy(A) oB = make_orcaflex_happy(B) entry['AddedMassMatrixX'] = oA[0].tolist() entry['AddedMassMatrixY'] = oA[1].tolist() entry['AddedMassMatrixZ'] = oA[2].tolist() entry['AddedMassMatrixRx'] = oA[3].tolist() entry['AddedMassMatrixRy'] = oA[4].tolist() entry['AddedMassMatrixRz'] = oA[5].tolist() entry['DampingX'] = oB[0].tolist() entry['DampingY'] = oB[1].tolist() entry['DampingZ'] = oB[2].tolist() entry['DampingRx'] = oB[3].tolist() entry['DampingRy'] = oB[4].tolist() entry['DampingRz'] = oB[5].tolist() FrequencyDependentAddedMassAndDamping.append(entry) d['AMDMethod'] = 'Frequency Dependent' d['FrequencyDependentAddedMassAndDamping'] = FrequencyDependentAddedMassAndDamping vt['Draughts'] = [d] # draughts is a list! Even though we only use one. # Create a vessel v = {'Name':n.name + 'Vessel', 'VesselType':n.name + hyd_spring.name, 'Length':1, 'InitialPosition': pos, 'InitialHeel': float(n.heel), 'InitialTrim': float(n.trim), 'InitialHeading': float(n.heading), 'IncludedInStatics': '6 DOF', 'PrimaryMotion': 'Calculated (6 DOF)', 'SuperimposedMotion': 'None', 'PrimaryMotionIsTreatedAs': 'Wave frequency', 'IncludeWaveLoad1stOrder': 'Yes', 'IncludeAddedMassAndDamping': 'Yes', 'IncludeOtherDamping': 'Yes'} # Modify the buoy to be on the vessel b['InitialPosition'] = [0,0,0] b['InitialAttitude'] = [0,0,0] b['Connection'] = v['Name'] vessel_types.append(vt) vessels.append(v) # Done with the vessel stuff, back to the 6D buoy that we were exporting buoys.append(b) if isinstance(n, Cable): connection = [] connectionX = [] connectionY = [] connectionZ = [] for c in n.connections: # either a point or a circle # for now only points are supported if isinstance(c, Circle): raise ValueError('Circles not yet supported') if c.parent is None: connection.append('Fixed') else: connection.append(c.parent.name) connectionX.append(c.position[0]) connectionY.append(c.position[1]) connectionZ.append(c.position[2]) w = { 'Name': n.name, 'Connection': connection, 'ConnectionX': connectionX, 'ConnectionY': connectionY, 'ConnectionZ': connectionZ, 'Stiffness': n.EA, 'NumberOfConnections': len(n.connections), 'WinchControlType': 'By Stage', 'StageMode': ['Specified Length','Specified Payout','Specified Payout'], 'StageValue': [n.length,0,0] } winches.append(w) if isinstance(n, Visual): visualfile = s.get_resource_path(n.path) if 'obj' in visualfile.suffix: # copy the .obj to the destination folder such that we have all required files in one place copy_from = visualfile copy_to = filename.parent / visualfile.name if copy_from == copy_to: pass else: copyfile(visualfile, filename.parent / visualfile.name) print(f'created {filename.parent / visualfile.name}') shape = { 'Name': n.name, 'Connection':n.parent.name, 'ShapeType':'Drawing', 'Shape' : 'Block', 'OriginX' : 0 , 'OriginY' : 0 , 'OriginZ' : 0 , 'Rotation1' : 0 , 'Rotation2' : 0 , 'Rotation3' : 0 , 'OutsidePenColour' : 55551, # $00D8FF 'ShadedDrawingFileName' : visualfile.name, 'ShadedDrawingMirrorInPlane' : 'XZ plane' , 'ShadedDrawingRotation1' : 0 , 'ShadedDrawingRotation2' : 90 , 'ShadedDrawingRotation3' : -90 } Shapes.append(shape) else: warn(f'Only .obj files can be used in orcaflex, not exporting visual "{n.name}"') if isinstance(n, Beam): # line-type typename = f"LT_for_{n.name}" mass_per_length = n.mass / n.L if mass_per_length < OFX_SMALL: print(f'Mass per length for {n.name} set to {OFX_SMALL}') mass_per_length = OFX_SMALL lt = {'Name' : typename, 'OD' : OFX_SMALL, 'ID' : 0, 'MassPerUnitLength' : mass_per_length, 'EIx': n.EIy, 'EIy' : n.EIz, 'GJ': n.GIp, 'CompressionIsLimited': yesno(n.tension_only), 'EA': n.EA } line_types.append(lt) line = OrderedDict({'Name':n.name, 'EndAConnection':n.nodeA.name, 'EndAX': 0, 'EndAY': 0, 'EndAZ': 0, 'EndAAzimuth' : 0, 'EndADeclination' : 90, 'EndBConnection': n.nodeB.name, 'EndBX': 0, 'EndBY': 0, 'EndBZ': 0, 'EndBAzimuth': 0, 'EndBDeclination': 90, 'EndAxBendingStiffness' : 'Infinity', 'EndBxBendingStiffness' : 'Infinity', 'EndAyBendingStiffness' : 'Infinity', 'EndByBendingStiffness' : 'Infinity', 'NumberOfSections':1, 'LineType[1]' : typename, 'Length[1]' : n.L, 'TargetSegmentLength[1]' : '~', 'StaticsStep1':'User specified' }) do_torsion = n.GIp > 0 if do_torsion: line['IncludeTorsion'] = 'Yes' line['EndATwistingStiffness'] = 'Infinity' line['EndBTwistingStiffness'] = 'Infinity' line['StartingShapeOrientationsSpecified'] = 'Yes' line['NumberOfSegments[1]'] = int(n.n_segments) pos = n.global_positions xx = pos[:,0] yy = pos[:,1] zz = pos[:,2] line['StartingShapeX'] = xx.tolist() line['StartingShapeY'] = yy.tolist() line['StartingShapeZ'] = zz.tolist() if do_torsion: rot = n.global_orientations rx = [] ry = [] rz = [] for r in rot: azdecgam = rotvec_to_line_node_axis_az_dec_gam(r) rx.append(azdecgam[0]) ry.append(azdecgam[1]) rz.append(azdecgam[2]) line['StartingShapeAzm'] = rx line['StartingShapeDec'] = ry line['StartingShapeGamma'] = rz lines.append(line) # Write the yml data = dict() if buoys: data['6DBuoys'] = buoys if winches: data['Winches'] = winches if constraints: data['Constraints'] = constraints if vessel_types: data['VesselTypes'] = vessel_types if vessels: data['Vessels'] = vessels if Shapes: data['Shapes'] = Shapes if line_types: data['LineTypes'] = line_types if lines: data['Lines'] = lines from yaml import CDumper as Dumper def dict_representer(dumper, data): return dumper.represent_dict(data.items()) Dumper.add_representer(OrderedDict, dict_representer) s = yaml.dump(data, explicit_start=True, Dumper=Dumper) with open(filename,'w') as f: f.write(s) print(f'created {filename}')
def test_extrapolate_on_damping_dir(): hyd = Hyddb1() hyd.load_from_capytaine(r"files/capytaine.nc") hyd.add_frequency(0) print(hyd.damping(0))
def test_extrapolate_on_force_dir(): hyd = Hyddb1() hyd.load_from_capytaine(r"files/capytaine.nc") hyd.add_direction(17) print(hyd.amass(17))
def test_extrapolate_on_force(): hyd = Hyddb1() hyd.load_from_capytaine(r"files/capytaine.nc") hyd.add_frequency(0) assert not np.any(np.isnan(hyd.force(0, 90)))
def test_extrapolate_on_addedmass(): hyd = Hyddb1() hyd.load_from_capytaine(r"files/capytaine.nc") hyd.add_frequencies([0]) assert not np.any(np.isnan(hyd.amass(0)))