|
2 | 2 | # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. |
3 | 3 | # SPDX-FileContributor: Martin Lemay, Paloma Martinez |
4 | 4 | import numpy as np |
| 5 | +import logging |
5 | 6 | import numpy.typing as npt |
6 | | -from typing import Iterator, List, Sequence, Any, Union |
7 | | -from vtkmodules.util.numpy_support import numpy_to_vtk |
8 | | -from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference |
9 | | -from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkMultiBlockDataSet, vtkPolyData, vtkDataSet, vtkDataObject, vtkPlane, vtkCellTypes, vtkIncrementalOctreePointLocator |
10 | | -from vtkmodules.vtkFiltersCore import vtk3DLinearGridPlaneCutter |
| 7 | +from typing import Iterator, List, Sequence, Any, Union, Tuple |
| 8 | +from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy ) |
| 9 | +from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray, vtkLogger, vtkFloatArray |
| 10 | +from vtkmodules.vtkCommonDataModel import ( |
| 11 | + vtkUnstructuredGrid, |
| 12 | + vtkMultiBlockDataSet, |
| 13 | + vtkPolyData, |
| 14 | + vtkDataSet, |
| 15 | + vtkDataObject, |
| 16 | + vtkPlane, |
| 17 | + vtkCellTypes, |
| 18 | + vtkIncrementalOctreePointLocator, |
| 19 | +) |
| 20 | +from vtkmodules.vtkFiltersCore import ( vtk3DLinearGridPlaneCutter, vtkPolyDataNormals, vtkPolyDataTangents ) |
| 21 | +from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane |
| 22 | + |
11 | 23 | from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockFromFlatIndex ) |
12 | 24 |
|
| 25 | +from geos.utils.algebraFunctions import ( |
| 26 | + getAttributeMatrixFromVector, |
| 27 | + getAttributeVectorFromMatrix, |
| 28 | +) |
| 29 | +from geos.utils.geometryFunctions import ( getChangeOfBasisMatrix, CANONICAL_BASIS_3D ) |
| 30 | +from geos.utils.Logger import ( getLogger, Logger, VTKCaptureLog, RegexExceptionFilter ) |
| 31 | +from geos.utils.Errors import VTKError |
| 32 | + |
13 | 33 | __doc__ = """ |
14 | 34 | Generic VTK utilities. |
15 | 35 |
|
@@ -267,3 +287,262 @@ def createVertices( cellPtsCoord: list[ npt.NDArray[ np.float64 ] ], |
267 | 287 | cellVertexMap += [ ptId.get() ] # type: ignore |
268 | 288 | cellVertexMapAll += [ tuple( cellVertexMap ) ] |
269 | 289 | return points, cellVertexMapAll |
| 290 | + |
| 291 | + |
| 292 | +def convertAttributeFromLocalToXYZForOneCell( |
| 293 | + vector: npt.NDArray[ np.float64 ], localBasisVectors: tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ], |
| 294 | + npt.NDArray[ np.float64 ] ] |
| 295 | +) -> npt.NDArray[ np.float64 ]: |
| 296 | + """Convert one cell attribute from local to XYZ basis. |
| 297 | +
|
| 298 | + .. Warning:: |
| 299 | + Vectors components are considered to be in GEOS output order such that \ |
| 300 | + v = ( M00, M11, M22, M12, M02, M01, M21, M20, M10 ) |
| 301 | +
|
| 302 | + Args: |
| 303 | + vector (npt.NDArray[np.float64]): The attribute expressed in local basis. The size of the vector must be 3, 9 or 6 (symmetrical case) |
| 304 | + localBasisVectors (tuple[ npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]): The local basis vectors expressed in the XYZ basis. |
| 305 | +
|
| 306 | + Returns: |
| 307 | + vectorXYZ (npt.NDArray[np.float64]): The attribute expressed in XYZ basis. |
| 308 | + """ |
| 309 | + # Get components matrix from GEOS attribute vector |
| 310 | + matrix3x3: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector ) |
| 311 | + |
| 312 | + # Get transform matrix |
| 313 | + transformMatrix: npt.NDArray[ np.float64 ] = getChangeOfBasisMatrix( localBasisVectors, CANONICAL_BASIS_3D ) |
| 314 | + |
| 315 | + # Apply transformation |
| 316 | + arrayXYZ: npt.NDArray[ np.float64 ] = transformMatrix @ matrix3x3 @ transformMatrix.T |
| 317 | + |
| 318 | + # Convert back to GEOS type attribute and return |
| 319 | + return getAttributeVectorFromMatrix( arrayXYZ, vector.size ) |
| 320 | + |
| 321 | + |
| 322 | +def getNormalVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]: |
| 323 | + """Return the normal vectors of a surface mesh. |
| 324 | +
|
| 325 | + Args: |
| 326 | + surface (vtkPolyData): The input surface. |
| 327 | +
|
| 328 | + Raises: |
| 329 | + ValueError: No normal attribute found in the mesh. Use the computeNormals function beforehand. |
| 330 | +
|
| 331 | + Returns: |
| 332 | + npt.NDArray[ np.float64 ]: The normal vectors of the input surface. |
| 333 | + """ |
| 334 | + normals: Union[ npt.NDArray[ np.float64 ], |
| 335 | + None ] = surface.GetCellData().GetNormals() # type: ignore[no-untyped-call] |
| 336 | + if normals is None: |
| 337 | + raise ValueError( "No normal attribute found in the mesh. Use the computeNormals function beforehand." ) |
| 338 | + |
| 339 | + return vtk_to_numpy( normals ) |
| 340 | + |
| 341 | + |
| 342 | +def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ]: |
| 343 | + """Return the tangential vectors of a surface. |
| 344 | +
|
| 345 | + Args: |
| 346 | + surface (vtkPolyData): The input surface. |
| 347 | +
|
| 348 | + Raises: |
| 349 | + ValueError: Tangent attribute not found in the mesh. Use computeTangents beforehand. |
| 350 | + ValueError: Problem during the calculation of the second tangential vectors. |
| 351 | +
|
| 352 | + Returns: |
| 353 | + Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: The tangents vectors of the input surface. |
| 354 | + """ |
| 355 | + # Get first tangential component |
| 356 | + vtkTangents: Union[ vtkFloatArray, None ] = surface.GetCellData().GetTangents() # type: ignore[no-untyped-call] |
| 357 | + tangents1: npt.NDArray[ np.float64 ] |
| 358 | + |
| 359 | + try: |
| 360 | + tangents1 = vtk_to_numpy( vtkTangents ) |
| 361 | + except AttributeError as err: |
| 362 | + print( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." ) |
| 363 | + raise VTKError( err ) from err |
| 364 | + else: |
| 365 | + # Compute second tangential component |
| 366 | + normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface ) |
| 367 | + |
| 368 | + tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 ) |
| 369 | + |
| 370 | + return ( tangents1, tangents2 ) |
| 371 | + |
| 372 | + |
| 373 | +def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]: |
| 374 | + """Return the local basis vectors for all cells of the input surface. |
| 375 | +
|
| 376 | + Args: |
| 377 | + surface(vtkPolydata): The input surface. |
| 378 | +
|
| 379 | + Returns: |
| 380 | + npt.NDArray[np.float64]: Array with normal, tangential 1 and tangential 2 vectors. |
| 381 | + """ |
| 382 | + try: |
| 383 | + normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface ) |
| 384 | + surfaceWithNormals: vtkPolyData = surface |
| 385 | + # ValueError raised if no normals found in the mesh |
| 386 | + except ValueError: |
| 387 | + # In that case, the normals are computed. |
| 388 | + surfaceWithNormals = computeNormals( surface ) |
| 389 | + normals = getNormalVectors( surfaceWithNormals ) |
| 390 | + |
| 391 | + # Tangents require normals to be present in the mesh |
| 392 | + try: |
| 393 | + tangents: Tuple[ npt.NDArray[ np.float64 ], |
| 394 | + npt.NDArray[ np.float64 ] ] = getTangentsVectors( surfaceWithNormals ) |
| 395 | + # If no tangents is present in the mesh, they are computed on that surface |
| 396 | + except VTKError: |
| 397 | + surfaceWithTangents: vtkPolyData = computeTangents( surfaceWithNormals ) |
| 398 | + tangents = getTangentsVectors( surfaceWithTangents ) |
| 399 | + |
| 400 | + return np.array( ( normals, *tangents ) ) |
| 401 | + |
| 402 | + |
| 403 | +def computeNormals( |
| 404 | + surface: vtkPolyData, |
| 405 | + logger: Union[ Logger, None ] = None, |
| 406 | +) -> vtkPolyData: |
| 407 | + """Compute and set the normals of a given surface. |
| 408 | +
|
| 409 | + Args: |
| 410 | + surface (vtkPolyData): The input surface. |
| 411 | + logger (Union[Logger, None], optional): A logger to manage the output messages. |
| 412 | + Defaults to None, an internal logger is used. |
| 413 | +
|
| 414 | + Returns: |
| 415 | + vtkPolyData: The surface with normal attribute. |
| 416 | + """ |
| 417 | + if logger is None: |
| 418 | + logger = getLogger( "computeSurfaceNormals" ) |
| 419 | + # Creation of a child logger to deal with VTKErrors without polluting parent logger |
| 420 | + vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" ) |
| 421 | + vtkErrorLogger.propagate = False |
| 422 | + |
| 423 | + vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR ) |
| 424 | + |
| 425 | + vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error |
| 426 | + vtkErrorLogger.setLevel( logging.DEBUG ) |
| 427 | + |
| 428 | + with VTKCaptureLog() as capturedLog: |
| 429 | + normalFilter: vtkPolyDataNormals = vtkPolyDataNormals() |
| 430 | + normalFilter.SetInputData( surface ) |
| 431 | + normalFilter.ComputeCellNormalsOn() |
| 432 | + normalFilter.ComputePointNormalsOff() |
| 433 | + normalFilter.Update() |
| 434 | + |
| 435 | + capturedLog.seek( 0 ) |
| 436 | + captured = capturedLog.read().decode() |
| 437 | + |
| 438 | + vtkErrorLogger.debug( captured.strip() ) |
| 439 | + |
| 440 | + outputSurface = normalFilter.GetOutput() |
| 441 | + |
| 442 | + if outputSurface.GetCellData().GetNormals() is None: |
| 443 | + raise VTKError( "Something went wrong with VTK calculation of the normals." ) |
| 444 | + |
| 445 | + return outputSurface |
| 446 | + |
| 447 | + |
| 448 | +def computeTangents( |
| 449 | + triangulatedSurface: vtkPolyData, |
| 450 | + logger: Union[ Logger, None ] = None, |
| 451 | +) -> vtkPolyData: |
| 452 | + """Compute and set the tangents of a given surface. |
| 453 | +
|
| 454 | + .. Warning:: The computation of tangents requires a triangulated surface. |
| 455 | +
|
| 456 | + Args: |
| 457 | + triangulatedSurface (vtkPolyData): The input surface. It should be triangulated beforehand. |
| 458 | + logger (Union[Logger, None], optional): A logger to manage the output messages. |
| 459 | + Defaults to None, an internal logger is used. |
| 460 | +
|
| 461 | + Returns: |
| 462 | + vtkPolyData: The surface with tangent attribute |
| 463 | + """ |
| 464 | + # need to compute texture coordinates required for tangent calculation |
| 465 | + surface1: vtkPolyData = computeSurfaceTextureCoordinates( triangulatedSurface ) |
| 466 | + |
| 467 | + # TODO: triangulate the surface before computation of the tangents if needed. |
| 468 | + |
| 469 | + # compute tangents |
| 470 | + if logger is None: |
| 471 | + logger = getLogger( "computeSurfaceTangents" ) |
| 472 | + # Creation of a child logger to deal with VTKErrors without polluting parent logger |
| 473 | + vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" ) |
| 474 | + vtkErrorLogger.propagate = False |
| 475 | + |
| 476 | + vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR ) |
| 477 | + |
| 478 | + vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error |
| 479 | + vtkErrorLogger.setLevel( logging.DEBUG ) |
| 480 | + |
| 481 | + with VTKCaptureLog() as capturedLog: |
| 482 | + |
| 483 | + tangentFilter: vtkPolyDataTangents = vtkPolyDataTangents() |
| 484 | + tangentFilter.SetInputData( surface1 ) |
| 485 | + tangentFilter.ComputeCellTangentsOn() |
| 486 | + tangentFilter.ComputePointTangentsOff() |
| 487 | + tangentFilter.Update() |
| 488 | + surfaceOut: vtkPolyData = tangentFilter.GetOutput() |
| 489 | + |
| 490 | + capturedLog.seek( 0 ) |
| 491 | + captured = capturedLog.read().decode() |
| 492 | + |
| 493 | + vtkErrorLogger.debug( captured.strip() ) |
| 494 | + |
| 495 | + if surfaceOut is None: |
| 496 | + raise VTKError( "Something went wrong in VTK calculation." ) |
| 497 | + |
| 498 | + # copy tangents attributes into filter input surface because surface attributes |
| 499 | + # are not transferred to the output (bug that should be corrected by Kitware) |
| 500 | + array: vtkDataArray = surfaceOut.GetCellData().GetTangents() |
| 501 | + if array is None: |
| 502 | + raise VTKError( "Attribute Tangents is not in the mesh." ) |
| 503 | + |
| 504 | + surface1.GetCellData().SetTangents( array ) |
| 505 | + surface1.GetCellData().Modified() |
| 506 | + surface1.Modified() |
| 507 | + return surface1 |
| 508 | + |
| 509 | + |
| 510 | +def computeSurfaceTextureCoordinates( |
| 511 | + surface: vtkPolyData, |
| 512 | + logger: Union[ Logger, None ] = None, |
| 513 | +) -> vtkPolyData: |
| 514 | + """Generate the 2D texture coordinates required for tangent computation. The dataset points are mapped onto a plane generated automatically. |
| 515 | +
|
| 516 | + Args: |
| 517 | + surface (vtkPolyData): The input surface. |
| 518 | + logger (Union[Logger, None], optional): A logger to manage the output messages. |
| 519 | + Defaults to None, an internal logger is used. |
| 520 | +
|
| 521 | + Returns: |
| 522 | + vtkPolyData: The input surface with generated texture map. |
| 523 | + """ |
| 524 | + # Need to compute texture coordinates required for tangent calculation |
| 525 | + if logger is None: |
| 526 | + logger = getLogger( "computeSurfaceTextureCoordinates" ) |
| 527 | + # Creation of a child logger to deal with VTKErrors without polluting parent logger |
| 528 | + vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" ) |
| 529 | + vtkErrorLogger.propagate = False |
| 530 | + |
| 531 | + vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR ) |
| 532 | + |
| 533 | + vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error |
| 534 | + vtkErrorLogger.setLevel( logging.DEBUG ) |
| 535 | + |
| 536 | + with VTKCaptureLog() as capturedLog: |
| 537 | + |
| 538 | + textureFilter: vtkTextureMapToPlane = vtkTextureMapToPlane() |
| 539 | + textureFilter.SetInputData( surface ) |
| 540 | + textureFilter.AutomaticPlaneGenerationOn() |
| 541 | + textureFilter.Update() |
| 542 | + |
| 543 | + capturedLog.seek( 0 ) |
| 544 | + captured = capturedLog.read().decode() |
| 545 | + |
| 546 | + vtkErrorLogger.debug( captured.strip() ) |
| 547 | + |
| 548 | + return textureFilter.GetOutput() |
0 commit comments