"""Volume mesh component"""from__future__importannotationsimportos.pathfromenumimportEnumfromtypingimportIterator,List,Optional,UnionimportnumpyasnpfrompydanticimportExtra,Field,validatorfromflow360.component.compress_uploadimportcompress_and_upload_chunksfrom..cloud.requestsimportCopyExampleVolumeMeshRequest,NewVolumeMeshRequestfrom..cloud.rest_apiimportRestApifrom..exceptionsimport(Flow360CloudFileError,Flow360FileError,Flow360NotImplementedError,Flow360RuntimeError,Flow360ValueError,)from..logimportlogfrom..solver_versionimportFlow360Versionfrom.caseimportCase,CaseDraftfrom.flow360_params.boundariesimportNoSlipWallfrom.flow360_params.flow360_paramsimport(Flow360MeshParams,Flow360Params,_GenericBoundaryWrapper,)from.flow360_params.params_baseimportparams_generic_validatorfrom.interfacesimportVolumeMeshInterfacefrom.meshing.paramsimportVolumeMeshingParamsfrom.resource_baseimport(Flow360Resource,Flow360ResourceBaseModel,Flow360ResourceListBase,ResourceDraft,)from.typesimportCOMMENTSfrom.utilsimportshared_account_confirm_proceed,validate_type,zstd_compressfrom.validatorimportValidatortry:importh5py_H5PY_AVAILABLE=TrueexceptImportError:_H5PY_AVAILABLE=Falsedefget_datatype(dataset):""" Get datatype of dataset :param dataset: :return: """data_raw=np.empty(dataset.shape,dataset.dtype)dataset.read_direct(data_raw)data_str="".join([chr(i)foriindataset])returndata_strdefget_no_slip_walls(params:Union[Flow360Params,Flow360MeshParams]):""" Get wall boundary names :param params: :return: """assertparamsif(isinstance(params,Flow360MeshParams)andparams.boundariesandparams.boundaries.no_slip_walls):returnparams.boundaries.no_slip_wallsifisinstance(params,Flow360Params)andparams.boundaries:return[wall_nameforwall_name,wallinparams.boundaries.dict().items()ifwall_name!=COMMENTSand_GenericBoundaryWrapper(v=wall).v.type==NoSlipWall().type]return[]defget_boundaries_from_sliding_interfaces(params:Union[Flow360Params,Flow360MeshParams]):""" Get wall boundary names :param params: :return: """assertparamsres=[]# Sliding interfaces are deprecated - we need to handle this somehow# if params.sliding_interfaces and params.sliding_interfaces.rotating_patches:# res += params.sliding_interfaces.rotating_patches[:]# if params.sliding_interfaces and params.sliding_interfaces.stationary_patches:# res += params.sliding_interfaces.stationary_patches[:]returnres# pylint: disable=too-many-branchesdefget_boundaries_from_file(cgns_file:str,solver_version:str=None):""" Get boundary names from CGNS file :param cgns_file: :param solver_version: :return: """names=[]withh5py.File(cgns_file,"r")ash5_file:base=h5_file["Base"]forzone_name,zoneinbase.items():ifzone_name==" data":continueifzone.attrs["label"].decode()!="Zone_t":continuezone_type=get_datatype(base[f"{zone_name}/ZoneType/ data"])ifzone_typenotin["Structured","Unstructured"]:continueforsection_name,sectioninzone.items():ifsection_name==" data":continueif"label"notinsection.attrs:continueifsolver_versionandFlow360Version(solver_version)<Flow360Version("release-22.2.1.0"):ifsection.attrs["label"].decode()!="Elements_t":continueelement_type_tag=int(zone[f"{section_name}/ data"][0])ifelement_type_tagin[5,7]:names.append(f"{zone_name}/{section_name}")ifelement_type_tag==20:first_element_type_tag=zone[f"{section_name}/ElementConnectivity/ data"][0]iffirst_element_type_tagin[5,7]:names.append(f"{zone_name}/{section_name}")else:ifsection.attrs["label"].decode()!="ZoneBC_t":continueforbc_name,bc_zoneinsection.items():ifbc_zone.attrs["label"].decode()=="BC_t":names.append(f"{zone_name}/{bc_name}")returnnamesdefvalidate_cgns(cgns_file:str,params:Union[Flow360Params,Flow360MeshParams],solver_version=None):""" Validate CGNS file :param cgns_file: :param params: :param solver_version: :return: """assertcgns_fileassertparamsboundaries_in_file=get_boundaries_from_file(cgns_file,solver_version)boundaries_in_params=get_no_slip_walls(params)+get_boundaries_from_sliding_interfaces(params)boundaries_in_file=set(boundaries_in_file)boundaries_in_params=set(boundaries_in_params)ifnotboundaries_in_file.issuperset(boundaries_in_params):raiseFlow360ValueError("The following input boundary names from mesh json are not found in mesh:"+f" {' '.join(boundaries_in_params-boundaries_in_file)}."+f" Boundary names in cgns: {' '.join(boundaries_in_file)}"+f" Boundary names in params: {' '.join(boundaries_in_file)}")log.info(f'Notice: {" ".join(boundaries_in_file-boundaries_in_params)} is '+"tagged as wall in cgns file, but not in input params")classVolumeMeshLog(Enum):""" Volume mesh log """USER_LOG="user.log"PY_LOG="validateFlow360Mesh.py.log"classVolumeMeshDownloadable(Enum):""" Volume mesh downloadable files """CONFIG_JSON="config.json"classVolumeMeshFileFormat(Enum):""" Volume mesh file format """UGRID="aflr3"CGNS="cgns"defext(self)->str:""" Get the extention for a file name. :return: """ifselfisVolumeMeshFileFormat.UGRID:return".ugrid"ifselfisVolumeMeshFileFormat.CGNS:return".cgns"return""@classmethoddefdetect(cls,file:str):""" detects mesh format from filename """ext=os.path.splitext(file)[1]ifext==VolumeMeshFileFormat.UGRID.ext():returnVolumeMeshFileFormat.UGRIDifext==VolumeMeshFileFormat.CGNS.ext():returnVolumeMeshFileFormat.CGNSraiseFlow360RuntimeError(f"Unsupported file format {file}")classUGRIDEndianness(Enum):""" UGRID endianness """LITTLE="little"BIG="big"NONE=Nonedefext(self)->str:""" Get the extention for a file name. :return: """ifselfisUGRIDEndianness.LITTLE:return".lb8"ifselfisUGRIDEndianness.BIG:return".b8"return""@classmethoddefdetect(cls,file:str):""" detects endianess UGRID mesh from filename """ifVolumeMeshFileFormat.detect(file)isnotVolumeMeshFileFormat.UGRID:returnUGRIDEndianness.NONEbasename=os.path.splitext(file)[0]ext=os.path.splitext(basename)[1]ifext==UGRIDEndianness.LITTLE.ext():returnUGRIDEndianness.LITTLEifext==UGRIDEndianness.BIG.ext():returnUGRIDEndianness.BIGraiseFlow360RuntimeError(f"Unknown endianness for file {file}")classCompressionFormat(Enum):""" Volume mesh file format """GZ="gz"BZ2="bz2"ZST="zst"NONE=Nonedefext(self)->str:""" Get the extention for a file name. :return: """ifselfisCompressionFormat.GZ:return".gz"ifselfisCompressionFormat.BZ2:return".bz2"ifselfisCompressionFormat.ZST:return".zst"return""@classmethoddefdetect(cls,file:str):""" detects compression from filename """file_name,ext=os.path.splitext(file)ifext==CompressionFormat.GZ.ext():returnCompressionFormat.GZ,file_nameifext==CompressionFormat.BZ2.ext():returnCompressionFormat.BZ2,file_nameifext==CompressionFormat.ZST.ext():returnCompressionFormat.ZST,file_namereturnCompressionFormat.NONE,file# pylint: disable=E0213classVolumeMeshMeta(Flow360ResourceBaseModel,extra=Extra.allow):""" VolumeMeshMeta component """id:str=Field(alias="meshId")name:str=Field(alias="meshName")created_at:str=Field(alias="meshAddTime")surface_mesh_id:Optional[str]=Field(alias="surfaceMeshId")mesh_params:Union[Flow360MeshParams,None,dict]=Field(alias="meshParams")mesh_format:Union[VolumeMeshFileFormat,None]=Field(alias="meshFormat")file_name:Union[str,None]=Field(alias="fileName")endianness:UGRIDEndianness=Field(alias="meshEndianness")compression:CompressionFormat=Field(alias="meshCompression")boundaries:Union[List,None]@validator("mesh_params",pre=True)definit_mesh_params(cls,value):""" validator for mesh_params """returnparams_generic_validator(value,Flow360MeshParams)@validator("endianness",pre=True)definit_endianness(cls,value):""" validator for endianess """returnUGRIDEndianness(value)orUGRIDEndianness.NONE@validator("compression",pre=True)definit_compression(cls,value):""" validator for compression """try:returnCompressionFormat(value)exceptValueError:returnCompressionFormat.NONEdefto_volume_mesh(self)->VolumeMesh:""" returns VolumeMesh object from volume mesh meta info """returnVolumeMesh(self.id)classVolumeMeshDraft(ResourceDraft):""" Volume mesh draft component (before submit) """# pylint: disable=too-many-arguments, too-many-instance-attributesdef__init__(self,file_name:str=None,params:Union[Flow360MeshParams,VolumeMeshingParams]=None,name:str=None,surface_mesh_id=None,tags:List[str]=None,solver_version=None,endianess:UGRIDEndianness=None,isascii:bool=False,):iffile_nameisnotNoneandnotos.path.exists(file_name):raiseFlow360FileError(f"File '{file_name}' not found.")ifendianessisnotNone:raiseFlow360NotImplementedError("endianess selections not supported, it is inferred from filename")ifisasciiisTrue:raiseFlow360NotImplementedError("isascii not supported")self.params=NoneifparamsisnotNone:ifnotisinstance(params,Flow360MeshParams)andnotisinstance(params,VolumeMeshingParams):raiseValueError(f"params={params} are not of type Flow360MeshParams OR VolumeMeshingParams")self.params=params.copy(deep=True)ifnameisNoneandfile_nameisnotNone:name=os.path.splitext(os.path.basename(file_name))[0]self.file_name=file_nameself.name=nameself.surface_mesh_id=surface_mesh_idself.tags=tagsself.solver_version=solver_versionself._id=Noneself.compress_method=CompressionFormat.ZSTResourceDraft.__init__(self)def_submit_from_surface(self):self.validator_api(self.params,solver_version=self.solver_version)body={"name":self.name,"tags":self.tags,"surfaceMeshId":self.surface_mesh_id,"config":self.params.flow360_json(),"format":"cgns",}ifself.solver_version:body["solverVersion"]=self.solver_versionresp=RestApi(VolumeMeshInterface.endpoint).post(body)ifnotresp:returnNoneinfo=VolumeMeshMeta(**resp)self._id=info.idmesh=VolumeMesh(self.id)log.info(f"VolumeMesh successfully submitted: {mesh.short_description()}")returnmesh# pylint: disable=protected-access, too-many-localsdef_submit_upload_mesh(self,progress_callback=None):assertos.path.exists(self.file_name)original_compression,file_name_no_compression=CompressionFormat.detect(self.file_name)mesh_format=VolumeMeshFileFormat.detect(file_name_no_compression)endianness=UGRIDEndianness.detect(file_name_no_compression)ifmesh_formatisVolumeMeshFileFormat.CGNS:remote_file_name="volumeMesh"else:remote_file_name="mesh"compression=(original_compressioniforiginal_compression!=CompressionFormat.NONEelseself.compress_method)remote_file_name=(f"{remote_file_name}{endianness.ext()}{mesh_format.ext()}{compression.ext()}")name=self.nameifnameisNone:name=os.path.splitext(os.path.basename(self.file_name))[0]req=NewVolumeMeshRequest(name=name,file_name=remote_file_name,tags=self.tags,format=mesh_format.value,endianness=endianness.value,compression=compression.value,params=self.params,solver_version=self.solver_version,)resp=RestApi(VolumeMeshInterface.endpoint).post(req.dict())ifnotresp:returnNoneinfo=VolumeMeshMeta(**resp)self._id=info.idmesh=VolumeMesh(self.id)# parallel compress and uploadif(original_compression==CompressionFormat.NONEandself.compress_method==CompressionFormat.BZ2):upload_id=mesh.create_multipart_upload(remote_file_name)compress_and_upload_chunks(self.file_name,upload_id,mesh,remote_file_name)elif(original_compression==CompressionFormat.NONEandself.compress_method==CompressionFormat.ZST):compressed_file_name=zstd_compress(self.file_name)mesh._upload_file(remote_file_name,compressed_file_name,progress_callback=progress_callback)os.remove(compressed_file_name)else:mesh._upload_file(remote_file_name,self.file_name,progress_callback=progress_callback)mesh._complete_upload(remote_file_name)log.info(f"VolumeMesh successfully uploaded: {mesh.short_description()}")returnmeshdefsubmit(self,progress_callback=None)->VolumeMesh:"""submit mesh to cloud Parameters ---------- progress_callback : callback, optional Use for custom progress bar, by default None Returns ------- VolumeMesh VolumeMesh object with id """ifnotshared_account_confirm_proceed():raiseFlow360ValueError("User aborted resource submit.")ifself.file_nameisnotNone:returnself._submit_upload_mesh(progress_callback)ifself.surface_mesh_idisnotNoneandself.nameisnotNoneandself.paramsisnotNone:returnself._submit_from_surface()raiseFlow360ValueError("You must provide volume mesh file for upload or surface mesh Id with meshing parameters.")@classmethoddefvalidator_api(cls,params:VolumeMeshingParams,solver_version=None):""" validation api: validates surface meshing parameters before submitting """returnValidator.VOLUME_MESH.validate(params,solver_version=solver_version)
def_complete_upload(self,remote_file_name):""" Complete volume mesh upload :return: """resp=self.post({},method=f"completeUpload?fileName={remote_file_name}")self._info=VolumeMeshMeta(**resp)@classmethoddef_interface(cls):returnVolumeMeshInterface@classmethoddef_meta_class(cls):""" returns volume mesh meta info class: VolumeMeshMeta """returnVolumeMeshMeta@classmethoddef_params_ancestor_id_name(cls):""" returns surfaceMeshId name """return"surfaceMeshId"
[docs]@classmethoddeffrom_cloud(cls,mesh_id:str):""" Get volume mesh info from cloud :param mesh_id: :return: """returncls(mesh_id)
def_get_file_extention(self):compression=self.info.compressionmesh_format=self.info.mesh_formatendianness=self.info.endiannessreturnf"{endianness.ext()}{mesh_format.ext()}{compression.ext()}"def_remote_file_name(self):""" mesh filename on cloud """remote_file_name=Noneforfileinself.get_download_file_list():_,file_name_no_compression=CompressionFormat.detect(file["fileName"])try:VolumeMeshFileFormat.detect(file_name_no_compression)remote_file_name=file["fileName"]exceptFlow360RuntimeError:continueifremote_file_nameisNone:raiseFlow360CloudFileError(f"No volume mesh file found for id={self.id}")returnremote_file_name
[docs]@classmethoddefcopy_from_example(cls,example_id:str,name:str=None,)->VolumeMesh:""" Create a new volume mesh by copying from an example mesh identified by `example_id`. Parameters ---------- example_id : str The unique identifier of the example volume mesh to copy from. name : str, optional The name to assign to the new volume mesh. If not provided, the name of the example volume mesh will be used. Returns ------- VolumeMesh A new instance of VolumeMesh copied from the example mesh if successful. Examples -------- >>> new_mesh = VolumeMesh.copy_from_example('example_id_123', name='New Mesh') """ifnameisNone:eg_vm=cls(example_id)name=eg_vm.namereq=CopyExampleVolumeMeshRequest(example_id=example_id,name=name)resp=RestApi(f"{VolumeMeshInterface.endpoint}/examples/copy").post(req.dict())ifnotresp:raiseRuntimeError("Something went wrong when accessing example mesh.")info=VolumeMeshMeta(**resp)returncls(info.id)
[docs]@classmethoddefcreate(cls,name:str,params:VolumeMeshingParams,surface_mesh_id,tags:List[str]=None,solver_version=None,)->VolumeMeshDraft:""" Create volume mesh from surface mesh """returnVolumeMeshDraft(name=name,surface_mesh_id=surface_mesh_id,solver_version=solver_version,params=params,tags=tags,)
[docs]defcreate_case(self,name:str,params:Flow360Params,tags:List[str]=None,solver_version:str=None,)->CaseDraft:""" Create new case :param name: :param params: :param tags: :return: """returnCase.create(name,params,volume_mesh_id=self.id,tags=tags,solver_version=solver_version)
classVolumeMeshList(Flow360ResourceListBase):""" VolumeMesh List component """def__init__(self,surface_mesh_id:str=None,from_cloud:bool=True,include_deleted:bool=False,limit=100,):super().__init__(ancestor_id=surface_mesh_id,from_cloud=from_cloud,include_deleted=include_deleted,limit=limit,resourceClass=VolumeMesh,)deffilter(self):""" flitering list, not implemented yet """raiseNotImplementedError("Filters are not implemented yet")# resp = list(filter(lambda i: i['caseStatus'] != 'deleted', resp))# pylint: disable=useless-parent-delegationdef__getitem__(self,index)->VolumeMesh:""" returns VolumeMeshMeta item of the list """returnsuper().__getitem__(index)# pylint: disable=useless-parent-delegationdef__iter__(self)->Iterator[VolumeMesh]:returnsuper().__iter__()