vedo.core
Base classes providing functionality to different vedo objects.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4 5import vedo.vtkclasses as vtk 6 7import vedo 8from vedo import colors 9from vedo import utils 10from vedo.transformations import LinearTransform, NonLinearTransform 11 12 13__docformat__ = "google" 14 15__doc__ = """ 16Base classes providing functionality to different vedo objects. 17 18 19""" 20 21__all__ = [ 22 "DataArrayHelper", 23 "CommonAlgorithms", 24 "PointAlgorithms", 25 "VolumeAlgorithms", 26] 27 28warnings = dict( 29 points_getter=( 30 "WARNING: points() is deprecated, use vertices instead. Change:\n" 31 " mesh.points() -> mesh.vertices\n" 32 " (silence this with vedo.core.warnings['points_getter']=False)" 33 ), 34 points_setter=( 35 "WARNING: points() is deprecated, use vertices instead. Change:\n" 36 " mesh.points([[x,y,z], ...]) -> mesh.vertices = [[x,y,z], ...]\n" 37 " (silence this with vedo.core.warnings['points_getter']=False)" 38 ), 39) 40 41############################################################################### 42class DataArrayHelper: 43 # Internal use only. 44 # Helper class to manage data associated to either 45 # points (or vertices) and cells (or faces). 46 def __init__(self, obj, association): 47 48 self.obj = obj 49 self.association = association 50 51 def __getitem__(self, key): 52 53 if self.association == 0: 54 data = self.obj.dataset.GetPointData() 55 56 elif self.association == 1: 57 data = self.obj.dataset.GetCellData() 58 59 elif self.association == 2: 60 data = self.obj.dataset.GetFieldData() 61 62 varr = data.GetAbstractArray(key) 63 if isinstance(varr, vtk.vtkStringArray): 64 if isinstance(key, int): 65 key = data.GetArrayName(key) 66 n = varr.GetNumberOfValues() 67 narr = [varr.GetValue(i) for i in range(n)] 68 return narr 69 ########### 70 71 else: 72 raise RuntimeError() 73 74 if isinstance(key, int): 75 key = data.GetArrayName(key) 76 77 arr = data.GetArray(key) 78 if not arr: 79 return None 80 return utils.vtk2numpy(arr) 81 82 def __setitem__(self, key, input_array): 83 84 if self.association == 0: 85 data = self.obj.dataset.GetPointData() 86 n = self.obj.dataset.GetNumberOfPoints() 87 self.obj.mapper.SetScalarModeToUsePointData() 88 89 elif self.association == 1: 90 data = self.obj.dataset.GetCellData() 91 n = self.obj.dataset.GetNumberOfCells() 92 self.obj.mapper.SetScalarModeToUseCellData() 93 94 elif self.association == 2: 95 data = self.obj.dataset.GetFieldData() 96 if not utils.is_sequence(input_array): 97 input_array = [input_array] 98 99 if isinstance(input_array[0], str): 100 varr = vtk.vtkStringArray() 101 varr.SetName(key) 102 varr.SetNumberOfComponents(1) 103 varr.SetNumberOfTuples(len(input_array)) 104 for i, iarr in enumerate(input_array): 105 if isinstance(iarr, np.ndarray): 106 iarr = iarr.tolist() # better format 107 # Note: a string k can be converted to numpy with 108 # import json; k = np.array(json.loads(k)) 109 varr.InsertValue(i, str(iarr)) 110 else: 111 try: 112 varr = utils.numpy2vtk(input_array, name=key) 113 except TypeError as e: 114 vedo.logger.error( 115 f"cannot create metadata with input object:\n" 116 f"{input_array}" 117 f"\n\nAllowed content examples are:\n" 118 f"- flat list of strings ['a','b', 1, [1,2,3], ...]" 119 f" (first item must be a string in this case)\n" 120 f" hint: use k = np.array(json.loads(k)) to convert strings\n" 121 f"- numpy arrays of any shape" 122 ) 123 raise e 124 125 data.AddArray(varr) 126 return ############ 127 128 else: 129 raise RuntimeError() 130 131 if len(input_array) != n: 132 vedo.logger.error( 133 f"Error in point/cell data: length of input {len(input_array)}" 134 f" != {n} nr. of elements" 135 ) 136 raise RuntimeError() 137 138 input_array = np.asarray(input_array) 139 varr = utils.numpy2vtk(input_array, name=key) 140 data.AddArray(varr) 141 142 if len(input_array.shape) == 1: # scalars 143 data.SetActiveScalars(key) 144 elif len(input_array.shape) == 2 and input_array.shape[1] == 3: # vectors 145 if key.lower() == "normals": 146 data.SetActiveNormals(key) 147 else: 148 data.SetActiveVectors(key) 149 150 def keys(self): 151 """Return the list of available data array names""" 152 if self.association == 0: 153 data = self.obj.dataset.GetPointData() 154 elif self.association == 1: 155 data = self.obj.dataset.GetCellData() 156 elif self.association == 2: 157 data = self.obj.dataset.GetFieldData() 158 arrnames = [] 159 for i in range(data.GetNumberOfArrays()): 160 if self.association == 2: 161 name = data.GetAbstractArray(i).GetName() 162 else: 163 name = data.GetArray(i).GetName() 164 if name: 165 arrnames.append(name) 166 return arrnames 167 168 def items(self): 169 """Return the list of available data array `(names, values)`.""" 170 if self.association == 0: 171 data = self.obj.dataset.GetPointData() 172 elif self.association == 1: 173 data = self.obj.dataset.GetCellData() 174 elif self.association == 2: 175 data = self.obj.dataset.GetFieldData() 176 arrnames = [] 177 for i in range(data.GetNumberOfArrays()): 178 if self.association == 2: 179 name = data.GetAbstractArray(i).GetName() 180 else: 181 name = data.GetArray(i).GetName() 182 if name: 183 arrnames.append((name, self[name])) 184 return arrnames 185 186 def todict(self): 187 """Return a dictionary of the available data arrays.""" 188 return dict(self.items()) 189 190 def rename(self, oldname, newname): 191 """Rename an array""" 192 if self.association == 0: 193 varr = self.obj.dataset.GetPointData().GetArray(oldname) 194 elif self.association == 1: 195 varr = self.obj.dataset.GetCellData().GetArray(oldname) 196 elif self.association == 2: 197 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 198 if varr: 199 varr.SetName(newname) 200 else: 201 vedo.logger.warning( 202 f"Cannot rename non existing array {oldname} to {newname}" 203 ) 204 205 def remove(self, key): 206 """Remove a data array by name or number""" 207 if self.association == 0: 208 self.obj.dataset.GetPointData().RemoveArray(key) 209 elif self.association == 1: 210 self.obj.dataset.GetCellData().RemoveArray(key) 211 elif self.association == 2: 212 self.obj.dataset.GetFieldData().RemoveArray(key) 213 214 def clear(self): 215 """Remove all data associated to this object""" 216 if self.association == 0: 217 data = self.obj.dataset.GetPointData() 218 elif self.association == 1: 219 data = self.obj.dataset.GetCellData() 220 elif self.association == 2: 221 data = self.obj.dataset.GetFieldData() 222 for i in range(data.GetNumberOfArrays()): 223 if self.association == 2: 224 name = data.GetAbstractArray(i).GetName() 225 else: 226 name = data.GetArray(i).GetName() 227 data.RemoveArray(name) 228 229 def select(self, key): 230 """Select one specific array by its name to make it the `active` one.""" 231 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 232 # and NOT mapped through the lookup table, while everything else is. 233 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 234 # types are treated as colors with values in the range 0-255 235 # and floating types are treated as colors with values in the range 0.0-1.0. 236 # Setting ColorModeToMapScalars means that all scalar data will be mapped 237 # through the lookup table. 238 # (Note that for multi-component scalars, the particular component 239 # to use for mapping can be specified using the SelectColorArray() method.) 240 if self.association == 0: 241 data = self.obj.dataset.GetPointData() 242 self.obj.mapper.SetScalarModeToUsePointData() 243 else: 244 data = self.obj.dataset.GetCellData() 245 self.obj.mapper.SetScalarModeToUseCellData() 246 247 if isinstance(key, int): 248 key = data.GetArrayName(key) 249 250 arr = data.GetArray(key) 251 if not arr: 252 return self.obj 253 254 nc = arr.GetNumberOfComponents() 255 # print("GetNumberOfComponents", nc) 256 if nc == 1: 257 data.SetActiveScalars(key) 258 elif nc == 2: 259 data.SetTCoords(arr) 260 elif nc == 3 or nc == 4: 261 if "rgb" in key.lower(): 262 data.SetActiveScalars(key) 263 try: 264 # could be a volume mapper 265 self.obj.mapper.SetColorModeToDirectScalars() 266 except AttributeError: 267 pass 268 else: 269 data.SetActiveVectors(key) 270 elif nc == 9: 271 data.SetActiveTensors(key) 272 else: 273 vedo.logger.error(f"Cannot select array {key} with {nc} components") 274 return self.obj 275 276 try: 277 # could be a volume mapper 278 self.obj.mapper.SetArrayName(key) 279 self.obj.mapper.ScalarVisibilityOn() 280 except AttributeError: 281 pass 282 283 return self.obj 284 285 def select_texture_coords(self, key): 286 """Select one specific array to be used as texture coordinates.""" 287 if self.association == 0: 288 data = self.obj.dataset.GetPointData() 289 else: 290 vedo.logger.warning("texture coordinates are only available for point data") 291 return 292 293 if isinstance(key, int): 294 key = data.GetArrayName(key) 295 data.SetTCoords(data.GetArray(key)) 296 return self.obj 297 298 def select_normals(self, key): 299 """Select one specific normal array by its name to make it the "active" one.""" 300 if self.association == 0: 301 data = self.obj.dataset.GetPointData() 302 self.obj.mapper.SetScalarModeToUsePointData() 303 else: 304 data = self.obj.dataset.GetCellData() 305 self.obj.mapper.SetScalarModeToUseCellData() 306 307 if isinstance(key, int): 308 key = data.GetArrayName(key) 309 data.SetActiveNormals(key) 310 return self.obj 311 312 def print(self, **kwargs): 313 """Print the array names available to terminal""" 314 colors.printc(self.keys(), **kwargs) 315 316 def __repr__(self) -> str: 317 """Representation""" 318 319 def _get_str(pd, header): 320 out = f"\x1b[2m\x1b[1m\x1b[7m{header}" 321 if pd.GetNumberOfArrays(): 322 if self.obj.name: 323 out += f" in {self.obj.name}" 324 out += f" contains {pd.GetNumberOfArrays()} array(s)\x1b[0m" 325 for i in range(pd.GetNumberOfArrays()): 326 varr = pd.GetArray(i) 327 out += f"\n\x1b[1m\x1b[4mArray name : {varr.GetName()}\x1b[0m" 328 out += "\nindex".ljust(15) + f": {i}" 329 t = varr.GetDataType() 330 if t in vedo.utils.array_types: 331 out += "\ntype".ljust(15) 332 out += f": {vedo.utils.array_types[t][1]} ({vedo.utils.array_types[t][0]})" 333 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 334 out += "\nshape".ljust(15) + f": {shape}" 335 out += "\nrange".ljust(15) + f": {np.array(varr.GetRange())}" 336 out += "\nmax id".ljust(15) + f": {varr.GetMaxId()}" 337 out += "\nlook up table".ljust(15) + f": {bool(varr.GetLookupTable())}" 338 out += "\nin-memory size".ljust(15) + f": {varr.GetActualMemorySize()} KB" 339 else: 340 out += " is empty.\x1b[0m" 341 return out 342 343 if self.association == 0: 344 out = _get_str(self.obj.dataset.GetPointData(), "Point Data") 345 elif self.association == 1: 346 out = _get_str(self.obj.dataset.GetCellData(), "Cell Data") 347 elif self.association == 2: 348 pd = self.obj.dataset.GetFieldData() 349 if pd.GetNumberOfArrays(): 350 out = "\x1b[2m\x1b[1m\x1b[7mMeta Data" 351 if self.obj.name: 352 out += f" in {self.obj.name}" 353 out += f" contains {pd.GetNumberOfArrays()} entries\x1b[0m" 354 for i in range(pd.GetNumberOfArrays()): 355 varr = pd.GetAbstractArray(i) 356 out += f"\n\x1b[1m\x1b[4mEntry name : {varr.GetName()}\x1b[0m" 357 out += "\nindex".ljust(15) + f": {i}" 358 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 359 out += "\nshape".ljust(15) + f": {shape}" 360 361 return out 362 363 364############################################################################### 365class CommonAlgorithms: 366 """Common algorithms.""" 367 368 def __init__(self): 369 # print("init CommonAlgorithms") 370 self.dataset = None 371 self.pipeline = None 372 self.name = "" 373 self.filename = "" 374 self.time = 0 375 376 377 @property 378 def pointdata(self): 379 """ 380 Create and/or return a `numpy.array` associated to points (vertices). 381 A data array can be indexed either as a string or by an integer number. 382 E.g.: `myobj.pointdata["arrayname"]` 383 384 Usage: 385 386 `myobj.pointdata.keys()` to return the available data array names 387 388 `myobj.pointdata.select(name)` to make this array the active one 389 390 `myobj.pointdata.remove(name)` to remove this array 391 """ 392 return DataArrayHelper(self, 0) 393 394 @property 395 def celldata(self): 396 """ 397 Create and/or return a `numpy.array` associated to cells (faces). 398 A data array can be indexed either as a string or by an integer number. 399 E.g.: `myobj.celldata["arrayname"]` 400 401 Usage: 402 403 `myobj.celldata.keys()` to return the available data array names 404 405 `myobj.celldata.select(name)` to make this array the active one 406 407 `myobj.celldata.remove(name)` to remove this array 408 """ 409 return DataArrayHelper(self, 1) 410 411 @property 412 def metadata(self): 413 """ 414 Create and/or return a `numpy.array` associated to neither cells nor faces. 415 A data array can be indexed either as a string or by an integer number. 416 E.g.: `myobj.metadata["arrayname"]` 417 418 Usage: 419 420 `myobj.metadata.keys()` to return the available data array names 421 422 `myobj.metadata.select(name)` to make this array the active one 423 424 `myobj.metadata.remove(name)` to remove this array 425 """ 426 return DataArrayHelper(self, 2) 427 428 def memory_address(self): 429 """ 430 Return a unique memory address integer which may serve as the ID of the 431 object, or passed to c++ code. 432 """ 433 # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/ 434 # https://github.com/tfmoraes/polydata_connectivity 435 return int(self.dataset.GetAddressAsString("")[5:], 16) 436 437 def memory_size(self): 438 """Return the size in bytes of the object in memory.""" 439 return self.dataset.GetActualMemorySize() 440 441 def modified(self): 442 """Use in conjunction with `tonumpy()` to update any modifications to the image array.""" 443 self.dataset.GetPointData().Modified() 444 self.dataset.GetPointData().GetScalars().Modified() 445 return self 446 447 def box(self, scale=1, padding=0): 448 """ 449 Return the bounding box as a new `Mesh` object. 450 451 Arguments: 452 scale : (float) 453 box size can be scaled by a factor 454 padding : (float, list) 455 a constant padding can be added (can be a list `[padx,pady,padz]`) 456 """ 457 b = self.bounds() 458 if not utils.is_sequence(padding): 459 padding = [padding, padding, padding] 460 length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4] 461 tol = (length + width + height) / 30000 # useful for boxing text 462 pos = [(b[0] + b[1])/2, (b[3] + b[2])/2, (b[5] + b[4])/2 - tol] 463 bx = vedo.shapes.Box( 464 pos, 465 length * scale + padding[0], 466 width * scale + padding[1], 467 height * scale + padding[2], 468 c="gray", 469 ) 470 try: 471 pr = vtk.vtkProperty() 472 pr.DeepCopy(self.properties) 473 bx.SetProperty(pr) 474 bx.properties = pr 475 except (AttributeError, TypeError): 476 pass 477 bx.flat().lighting("off").wireframe(True) 478 return bx 479 480 def bounds(self): 481 """ 482 Get the object bounds. 483 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 484 """ 485 try: # this is very slow for large meshes 486 pts = self.vertices 487 xmin, ymin, zmin = np.min(pts, axis=0) 488 xmax, ymax, zmax = np.max(pts, axis=0) 489 return np.array([xmin, xmax, ymin, ymax, zmin, zmax]) 490 except (AttributeError, ValueError): 491 return np.array(self.dataset.GetBounds()) 492 493 def xbounds(self, i=None): 494 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 495 b = self.bounds() 496 if i is not None: 497 return b[i] 498 return np.array([b[0], b[1]]) 499 500 def ybounds(self, i=None): 501 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 502 b = self.bounds() 503 if i == 0: 504 return b[2] 505 if i == 1: 506 return b[3] 507 return np.array([b[2], b[3]]) 508 509 def zbounds(self, i=None): 510 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 511 b = self.bounds() 512 if i == 0: 513 return b[4] 514 if i == 1: 515 return b[5] 516 return np.array([b[4], b[5]]) 517 518 def diagonal_size(self): 519 """Get the length of the diagonal of the bounding box.""" 520 b = self.bounds() 521 return np.sqrt((b[1] - b[0])**2 + (b[3] - b[2])**2 + (b[5] - b[4])**2) 522 523 def average_size(self): 524 """ 525 Calculate and return the average size of the object. 526 This is the mean of the vertex distances from the center of mass. 527 """ 528 coords = self.vertices 529 cm = np.mean(coords, axis=0) 530 if coords.shape[0] == 0: 531 return 0.0 532 cc = coords - cm 533 return np.mean(np.linalg.norm(cc, axis=1)) 534 535 def center_of_mass(self): 536 """Get the center of mass of the object.""" 537 cmf = vtk.new("CenterOfMass") 538 cmf.SetInputData(self.dataset) 539 cmf.Update() 540 c = cmf.GetCenter() 541 return np.array(c) 542 543 def copy_data_from(self, obj): 544 """Copy all data (point and cell data) from this input object""" 545 self.dataset.GetPointData().PassData(obj.dataset.GetPointData()) 546 self.dataset.GetCellData().PassData(obj.dataset.GetCellData()) 547 self.pipeline = utils.OperationNode( 548 "copy_data_from", 549 parents=[self, obj], 550 comment=f"{obj.__class__.__name__}", 551 shape="note", 552 c="#ccc5b9", 553 ) 554 return self 555 556 def inputdata(self): 557 """Obsolete, use `.dataset` instead.""" 558 colors.printc("WARNING: 'inputdata()' is obsolete, use '.dataset' instead.", c="y") 559 return self.dataset 560 561 @property 562 def npoints(self): 563 """Retrieve the number of points (or vertices).""" 564 return self.dataset.GetNumberOfPoints() 565 566 @property 567 def nvertices(self): 568 """Retrieve the number of vertices (or points).""" 569 return self.dataset.GetNumberOfPoints() 570 571 @property 572 def ncells(self): 573 """Retrieve the number of cells.""" 574 return self.dataset.GetNumberOfCells() 575 576 def points(self, pts=None): 577 """Obsolete, use `self.vertices` or `self.coordinates` instead.""" 578 if pts is None: ### getter 579 580 if warnings["points_getter"]: 581 colors.printc(warnings["points_getter"], c="y") 582 warnings["points_getter"] = "" 583 return self.vertices 584 585 else: ### setter 586 587 if warnings["points_setter"]: 588 colors.printc(warnings["points_setter"], c="y") 589 warnings["points_setter"] = "" 590 591 pts = np.asarray(pts, dtype=np.float32) 592 593 if pts.ndim == 1: 594 ### getter by point index ################### 595 indices = pts.astype(int) 596 vpts = self.dataset.GetPoints() 597 arr = utils.vtk2numpy(vpts.GetData()) 598 return arr[indices] ########### 599 600 ### setter #################################### 601 if pts.shape[1] == 2: 602 pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)] 603 arr = utils.numpy2vtk(pts, dtype=np.float32) 604 605 vpts = self.dataset.GetPoints() 606 vpts.SetData(arr) 607 vpts.Modified() 608 # reset mesh to identity matrix position/rotation: 609 self.point_locator = None 610 self.cell_locator = None 611 self.line_locator = None 612 self.transform = LinearTransform() 613 return self 614 615 @property 616 def cell_centers(self): 617 """ 618 Get the coordinates of the cell centers. 619 620 Examples: 621 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 622 """ 623 vcen = vtk.new("CellCenters") 624 vcen.SetInputData(self.dataset) 625 vcen.Update() 626 return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData()) 627 628 @property 629 def lines(self): 630 """ 631 Get lines connectivity ids as a python array 632 formatted as `[[id0,id1], [id3,id4], ...]` 633 634 See also: `lines_as_flat_array()`. 635 """ 636 # Get cell connettivity ids as a 1D array. The vtk format is: 637 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 638 arr1d = utils.vtk2numpy(self.dataset.GetLines().GetData()) 639 i = 0 640 conn = [] 641 n = len(arr1d) 642 for _ in range(n): 643 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 644 conn.append(cell) 645 i += arr1d[i] + 1 646 if i >= n: 647 break 648 649 return conn # cannot always make a numpy array of it! 650 651 @property 652 def lines_as_flat_array(self): 653 """ 654 Get lines connectivity ids as a 1D numpy array. 655 Format is e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 656 657 See also: `lines()`. 658 """ 659 return utils.vtk2numpy(self.dataset.GetLines().GetData()) 660 661 def mark_boundaries(self): 662 """ 663 Mark cells and vertices if they lie on a boundary. 664 A new array called `BoundaryCells` is added to the object. 665 """ 666 mb = vtk.new("MarkBoundaryFilter") 667 mb.SetInputData(self.dataset) 668 mb.Update() 669 self.dataset.DeepCopy(mb.GetOutput()) 670 self.pipeline = utils.OperationNode("mark_boundaries", parents=[self]) 671 return self 672 673 def find_cells_in_bounds(self, xbounds=(), ybounds=(), zbounds=()): 674 """ 675 Find cells that are within the specified bounds. 676 """ 677 try: 678 xbounds = list(xbounds.bounds()) 679 except AttributeError: 680 pass 681 682 if len(xbounds) == 6: 683 bnds = xbounds 684 else: 685 bnds = list(self.bounds()) 686 if len(xbounds) == 2: 687 bnds[0] = xbounds[0] 688 bnds[1] = xbounds[1] 689 if len(ybounds) == 2: 690 bnds[2] = ybounds[0] 691 bnds[3] = ybounds[1] 692 if len(zbounds) == 2: 693 bnds[4] = zbounds[0] 694 bnds[5] = zbounds[1] 695 696 cell_ids = vtk.vtkIdList() 697 if not self.cell_locator: 698 self.cell_locator = vtk.new("CellTreeLocator") 699 self.cell_locator.SetDataSet(self.dataset) 700 self.cell_locator.BuildLocator() 701 self.cell_locator.FindCellsWithinBounds(bnds, cell_ids) 702 cids = [] 703 for i in range(cell_ids.GetNumberOfIds()): 704 cid = cell_ids.GetId(i) 705 cids.append(cid) 706 return np.array(cids) 707 708 def find_cells_along_line(self, p0, p1, tol=0.001): 709 """ 710 Find cells that are intersected by a line segment. 711 """ 712 cell_ids = vtk.vtkIdList() 713 if not self.cell_locator: 714 self.cell_locator = vtk.new("CellTreeLocator") 715 self.cell_locator.SetDataSet(self.dataset) 716 self.cell_locator.BuildLocator() 717 self.cell_locator.FindCellsAlongLine(p0, p1, tol, cell_ids) 718 cids = [] 719 for i in range(cell_ids.GetNumberOfIds()): 720 cid = cell_ids.GetId(i) 721 cids.append(cid) 722 return np.array(cids) 723 724 def find_cells_along_plane(self, origin, normal, tol=0.001): 725 """ 726 Find cells that are intersected by a plane. 727 """ 728 cell_ids = vtk.vtkIdList() 729 if not self.cell_locator: 730 self.cell_locator = vtk.new("CellTreeLocator") 731 self.cell_locator.SetDataSet(self.dataset) 732 self.cell_locator.BuildLocator() 733 self.cell_locator.FindCellsAlongPlane(origin, normal, tol, cell_ids) 734 cids = [] 735 for i in range(cell_ids.GetNumberOfIds()): 736 cid = cell_ids.GetId(i) 737 cids.append(cid) 738 return np.array(cids) 739 740 def delete_cells_by_point_index(self, indices): 741 """ 742 Delete a list of vertices identified by any of their vertex index. 743 744 See also `delete_cells()`. 745 746 Examples: 747 - [delete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delete_mesh_pts.py) 748 749  750 """ 751 cell_ids = vtk.vtkIdList() 752 self.dataset.BuildLinks() 753 n = 0 754 for i in np.unique(indices): 755 self.dataset.GetPointCells(i, cell_ids) 756 for j in range(cell_ids.GetNumberOfIds()): 757 self.dataset.DeleteCell(cell_ids.GetId(j)) # flag cell 758 n += 1 759 760 self.dataset.RemoveDeletedCells() 761 self.dataset.Modified() 762 self.pipeline = utils.OperationNode( 763 "delete_cells_by_point_index", parents=[self]) 764 return self 765 766 def map_cells_to_points(self, arrays=(), move=False): 767 """ 768 Interpolate cell data (i.e., data specified per cell or face) 769 into point data (i.e., data specified at each vertex). 770 The method of transformation is based on averaging the data values 771 of all cells using a particular point. 772 773 A custom list of arrays to be mapped can be passed in input. 774 775 Set `move=True` to delete the original `celldata` array. 776 """ 777 c2p = vtk.new("CellDataToPointData") 778 c2p.SetInputData(self.dataset) 779 if not move: 780 c2p.PassCellDataOn() 781 if arrays: 782 c2p.ClearCellDataArrays() 783 c2p.ProcessAllArraysOff() 784 for arr in arrays: 785 c2p.AddCellDataArray(arr) 786 else: 787 c2p.ProcessAllArraysOn() 788 c2p.Update() 789 self._update(c2p.GetOutput(), reset_locators=False) 790 self.mapper.SetScalarModeToUsePointData() 791 self.pipeline = utils.OperationNode("map_cells_to_points", parents=[self]) 792 return self 793 794 @property 795 def vertices(self): 796 """Return the vertices (points) coordinates.""" 797 try: 798 # valid for polydata and unstructured grid 799 varr = self.dataset.GetPoints().GetData() 800 801 except AttributeError: 802 try: 803 # valid for rectilinear/structured grid, image data 804 v2p = vtk.new("ImageToPoints") 805 v2p.SetInputData(self.dataset) 806 v2p.Update() 807 varr = v2p.GetOutput().GetPoints().GetData() 808 except AttributeError: 809 return np.array([]) 810 811 narr = utils.vtk2numpy(varr) 812 return narr 813 814 # setter 815 @vertices.setter 816 def vertices(self, pts): 817 """Set vertices (points) coordinates.""" 818 pts = utils.make3d(pts) 819 arr = utils.numpy2vtk(pts, dtype=np.float32) 820 try: 821 vpts = self.dataset.GetPoints() 822 vpts.SetData(arr) 823 vpts.Modified() 824 except AttributeError: 825 vedo.logger.error(f"Cannot set vertices for object {type(self)}") 826 return self 827 # reset mesh to identity matrix position/rotation: 828 self.point_locator = None 829 self.cell_locator = None 830 self.line_locator = None 831 self.transform = LinearTransform() 832 return self 833 834 835 @property 836 def coordinates(self): 837 """Return the vertices (points) coordinates. Same as `vertices`.""" 838 return self.vertices 839 840 @coordinates.setter 841 def coordinates(self, pts): 842 """Set vertices (points) coordinates. Same as `vertices`.""" 843 self.vertices = pts 844 845 @property 846 def cells_as_flat_array(self): 847 """ 848 Get cell connectivity ids as a 1D numpy array. 849 Format is e.g. [3, 10,20,30 4, 10,11,12,13 ...] 850 """ 851 try: 852 # valid for unstructured grid 853 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 854 except AttributeError: 855 # valid for polydata 856 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 857 # if arr1d.size == 0: 858 # arr1d = utils.vtk2numpy(self.dataset.GetStrips().GetData()) 859 return arr1d 860 861 @property 862 def cells(self): 863 """ 864 Get the cells connectivity ids as a numpy array. 865 866 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 867 """ 868 try: 869 # valid for unstructured grid 870 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 871 except AttributeError: 872 # valid for polydata 873 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 874 # if arr1d.size == 0: 875 # arr1d = utils.vtk2numpy(self.dataset.GetStrips().GetData()) 876 877 # Get cell connettivity ids as a 1D array. vtk format is: 878 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 879 i = 0 880 conn = [] 881 n = len(arr1d) 882 if n: 883 while True: 884 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 885 conn.append(cell) 886 i += arr1d[i] + 1 887 if i >= n: 888 break 889 return conn 890 891 def map_points_to_cells(self, arrays=(), move=False): 892 """ 893 Interpolate point data (i.e., data specified per point or vertex) 894 into cell data (i.e., data specified per cell). 895 The method of transformation is based on averaging the data values 896 of all points defining a particular cell. 897 898 A custom list of arrays to be mapped can be passed in input. 899 900 Set `move=True` to delete the original `pointdata` array. 901 902 Examples: 903 - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py) 904 """ 905 p2c = vtk.new("PointDataToCellData") 906 p2c.SetInputData(self.dataset) 907 if not move: 908 p2c.PassPointDataOn() 909 if arrays: 910 p2c.ClearPointDataArrays() 911 p2c.ProcessAllArraysOff() 912 for arr in arrays: 913 p2c.AddPointDataArray(arr) 914 else: 915 p2c.ProcessAllArraysOn() 916 p2c.Update() 917 self._update(p2c.GetOutput(), reset_locators=False) 918 self.mapper.SetScalarModeToUseCellData() 919 self.pipeline = utils.OperationNode("map_points_to_cells", parents=[self]) 920 return self 921 922 def resample_data_from(self, source, tol=None, categorical=False): 923 """ 924 Resample point and cell data from another dataset. 925 The output has the same structure but its point data have 926 the resampled values from target. 927 928 Use `tol` to set the tolerance used to compute whether 929 a point in the source is in a cell of the current object. 930 Points without resampled values, and their cells, are marked as blank. 931 If the data is categorical, then the resulting data will be determined 932 by a nearest neighbor interpolation scheme. 933 934 Example: 935 ```python 936 from vedo import * 937 m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1) 938 pts = m1.vertices 939 ces = m1.cell_centers 940 m1.pointdata["xvalues"] = np.power(pts[:,0], 3) 941 m1.celldata["yvalues"] = np.power(ces[:,1], 3) 942 m2 = Mesh(dataurl+'bunny.obj') 943 m2.resample_arrays_from(m1) 944 # print(m2.pointdata["xvalues"]) 945 show(m1, m2 , N=2, axes=1) 946 ``` 947 """ 948 rs = vtk.new("ResampleWithDataSet") 949 rs.SetInputData(self.dataset) 950 rs.SetSourceData(source.dataset) 951 952 rs.SetPassPointArrays(True) 953 rs.SetPassCellArrays(True) 954 rs.SetPassFieldArrays(True) 955 rs.SetCategoricalData(categorical) 956 957 rs.SetComputeTolerance(True) 958 if tol: 959 rs.SetComputeTolerance(False) 960 rs.SetTolerance(tol) 961 rs.Update() 962 self._update(rs.GetOutput(), reset_locators=False) 963 self.pipeline = utils.OperationNode( 964 "resample_data_from", 965 comment=f"{source.__class__.__name__}", 966 parents=[self, source] 967 ) 968 return self 969 970 def interpolate_data_from( 971 self, 972 source, 973 radius=None, 974 n=None, 975 kernel="shepard", 976 exclude=("Normals",), 977 on="points", 978 null_strategy=1, 979 null_value=0, 980 ): 981 """ 982 Interpolate over source to port its data onto the current object using various kernels. 983 984 If n (number of closest points to use) is set then radius value is ignored. 985 986 Check out also: 987 `probe()` which in many cases can be faster. 988 989 Arguments: 990 kernel : (str) 991 available kernels are [shepard, gaussian, linear] 992 null_strategy : (int) 993 specify a strategy to use when encountering a "null" point 994 during the interpolation process. Null points occur when the local neighborhood 995 (of nearby points to interpolate from) is empty. 996 997 - Case 0: an output array is created that marks points 998 as being valid (=1) or null (invalid =0), and the null_value is set as well 999 - Case 1: the output data value(s) are set to the provided null_value 1000 - Case 2: simply use the closest point to perform the interpolation. 1001 null_value : (float) 1002 see above. 1003 1004 Examples: 1005 - [interpolate_scalar1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar1.py) 1006 - [interpolate_scalar3.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar3.py) 1007 - [interpolate_scalar4.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar4.py) 1008 - [image_probe.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/image_probe.py) 1009 1010  1011 """ 1012 if radius is None and not n: 1013 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 1014 raise RuntimeError 1015 1016 if on == "points": 1017 points = source.dataset 1018 elif on == "cells": 1019 c2p = vtk.new("CellDataToPointData") 1020 c2p.SetInputData(source.dataset) 1021 c2p.Update() 1022 points = c2p.GetOutput() 1023 else: 1024 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 1025 raise RuntimeError() 1026 1027 locator = vtk.new("PointLocator") 1028 locator.SetDataSet(points) 1029 locator.BuildLocator() 1030 1031 if kernel.lower() == "shepard": 1032 kern = vtk.new("ShepardKernel") 1033 kern.SetPowerParameter(2) 1034 elif kernel.lower() == "gaussian": 1035 kern = vtk.new("GaussianKernel") 1036 kern.SetSharpness(2) 1037 elif kernel.lower() == "linear": 1038 kern = vtk.new("LinearKernel") 1039 else: 1040 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 1041 raise RuntimeError() 1042 1043 if n: 1044 kern.SetNumberOfPoints(n) 1045 kern.SetKernelFootprintToNClosest() 1046 else: 1047 kern.SetRadius(radius) 1048 kern.SetKernelFootprintToRadius() 1049 1050 interpolator = vtk.new("PointInterpolator") 1051 interpolator.SetInputData(self.dataset) 1052 interpolator.SetSourceData(points) 1053 interpolator.SetKernel(kern) 1054 interpolator.SetLocator(locator) 1055 interpolator.PassFieldArraysOff() 1056 interpolator.SetNullPointsStrategy(null_strategy) 1057 interpolator.SetNullValue(null_value) 1058 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 1059 for ex in exclude: 1060 interpolator.AddExcludedArray(ex) 1061 interpolator.Update() 1062 1063 if on == "cells": 1064 p2c = vtk.new("PointDataToCellData") 1065 p2c.SetInputData(interpolator.GetOutput()) 1066 p2c.Update() 1067 cpoly = p2c.GetOutput() 1068 else: 1069 cpoly = interpolator.GetOutput() 1070 1071 self._update(cpoly, reset_locators=False) 1072 1073 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 1074 return self 1075 1076 def add_ids(self): 1077 """ 1078 Generate point and cell ids arrays. 1079 1080 Two new arrays are added to the mesh: `PointID` and `CellID`. 1081 """ 1082 ids = vtk.new("IdFilter") 1083 ids.SetInputData(self.dataset) 1084 ids.PointIdsOn() 1085 ids.CellIdsOn() 1086 ids.FieldDataOff() 1087 ids.SetPointIdsArrayName("PointID") 1088 ids.SetCellIdsArrayName("CellID") 1089 ids.Update() 1090 self._update(ids.GetOutput(), reset_locators=False) 1091 self.pipeline = utils.OperationNode("add_ids", parents=[self]) 1092 return self 1093 1094 def gradient(self, input_array=None, on="points", fast=False): 1095 """ 1096 Compute and return the gradiend of the active scalar field as a numpy array. 1097 1098 Arguments: 1099 input_array : (str) 1100 array of the scalars to compute the gradient, 1101 if None the current active array is selected 1102 on : (str) 1103 compute either on 'points' or 'cells' data 1104 fast : (bool) 1105 if True, will use a less accurate algorithm 1106 that performs fewer derivative calculations (and is therefore faster). 1107 1108 Examples: 1109 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py) 1110 1111  1112 """ 1113 gra = vtk.new("GradientFilter") 1114 if on.startswith("p"): 1115 varr = self.dataset.GetPointData() 1116 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1117 elif on.startswith("c"): 1118 varr = self.dataset.GetCellData() 1119 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1120 else: 1121 vedo.logger.error(f"in gradient: unknown option {on}") 1122 raise RuntimeError 1123 1124 if input_array is None: 1125 if varr.GetScalars(): 1126 input_array = varr.GetScalars().GetName() 1127 else: 1128 vedo.logger.error(f"in gradient: no scalars found for {on}") 1129 raise RuntimeError 1130 1131 gra.SetInputData(self.dataset) 1132 gra.SetInputScalars(tp, input_array) 1133 gra.SetResultArrayName("Gradient") 1134 gra.SetFasterApproximation(fast) 1135 gra.ComputeDivergenceOff() 1136 gra.ComputeVorticityOff() 1137 gra.ComputeGradientOn() 1138 gra.Update() 1139 if on.startswith("p"): 1140 gvecs = utils.vtk2numpy(gra.GetOutput().GetPointData().GetArray("Gradient")) 1141 else: 1142 gvecs = utils.vtk2numpy(gra.GetOutput().GetCellData().GetArray("Gradient")) 1143 return gvecs 1144 1145 def divergence(self, array_name=None, on="points", fast=False): 1146 """ 1147 Compute and return the divergence of a vector field as a numpy array. 1148 1149 Arguments: 1150 array_name : (str) 1151 name of the array of vectors to compute the divergence, 1152 if None the current active array is selected 1153 on : (str) 1154 compute either on 'points' or 'cells' data 1155 fast : (bool) 1156 if True, will use a less accurate algorithm 1157 that performs fewer derivative calculations (and is therefore faster). 1158 """ 1159 div = vtk.new("GradientFilter") 1160 if on.startswith("p"): 1161 varr = self.dataset.GetPointData() 1162 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1163 elif on.startswith("c"): 1164 varr = self.dataset.GetCellData() 1165 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1166 else: 1167 vedo.logger.error(f"in divergence(): unknown option {on}") 1168 raise RuntimeError 1169 1170 if array_name is None: 1171 if varr.GetVectors(): 1172 array_name = varr.GetVectors().GetName() 1173 else: 1174 vedo.logger.error(f"in divergence(): no vectors found for {on}") 1175 raise RuntimeError 1176 1177 div.SetInputData(self.dataset) 1178 div.SetInputScalars(tp, array_name) 1179 div.ComputeDivergenceOn() 1180 div.ComputeGradientOff() 1181 div.ComputeVorticityOff() 1182 div.SetDivergenceArrayName("Divergence") 1183 div.SetFasterApproximation(fast) 1184 div.Update() 1185 if on.startswith("p"): 1186 dvecs = utils.vtk2numpy(div.GetOutput().GetPointData().GetArray("Divergence")) 1187 else: 1188 dvecs = utils.vtk2numpy(div.GetOutput().GetCellData().GetArray("Divergence")) 1189 return dvecs 1190 1191 def vorticity(self, array_name=None, on="points", fast=False): 1192 """ 1193 Compute and return the vorticity of a vector field as a numpy array. 1194 1195 Arguments: 1196 array_name : (str) 1197 name of the array to compute the vorticity, 1198 if None the current active array is selected 1199 on : (str) 1200 compute either on 'points' or 'cells' data 1201 fast : (bool) 1202 if True, will use a less accurate algorithm 1203 that performs fewer derivative calculations (and is therefore faster). 1204 """ 1205 vort = vtk.new("GradientFilter") 1206 if on.startswith("p"): 1207 varr = self.dataset.GetPointData() 1208 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1209 elif on.startswith("c"): 1210 varr = self.dataset.GetCellData() 1211 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1212 else: 1213 vedo.logger.error(f"in vorticity(): unknown option {on}") 1214 raise RuntimeError 1215 1216 if array_name is None: 1217 if varr.GetVectors(): 1218 array_name = varr.GetVectors().GetName() 1219 else: 1220 vedo.logger.error(f"in vorticity(): no vectors found for {on}") 1221 raise RuntimeError 1222 1223 vort.SetInputData(self.dataset) 1224 vort.SetInputScalars(tp, array_name) 1225 vort.ComputeDivergenceOff() 1226 vort.ComputeGradientOff() 1227 vort.ComputeVorticityOn() 1228 vort.SetVorticityArrayName("Vorticity") 1229 vort.SetFasterApproximation(fast) 1230 vort.Update() 1231 if on.startswith("p"): 1232 vvecs = utils.vtk2numpy(vort.GetOutput().GetPointData().GetArray("Vorticity")) 1233 else: 1234 vvecs = utils.vtk2numpy(vort.GetOutput().GetCellData().GetArray("Vorticity")) 1235 return vvecs 1236 1237 def probe(self, source): 1238 """ 1239 Takes a `Volume` (or any other data set) 1240 and probes its scalars at the specified points in space. 1241 1242 Note that a mask is also output with valid/invalid points which can be accessed 1243 with `mesh.pointdata['ValidPointMask']`. 1244 1245 Check out also: 1246 `interpolate_data_from()` 1247 1248 Examples: 1249 - [probe_points.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_points.py) 1250 1251  1252 """ 1253 probe_filter = vtk.new("ProbeFilter") 1254 probe_filter.SetSourceData(source.dataset) 1255 probe_filter.SetInputData(self.dataset) 1256 probe_filter.Update() 1257 self._update(probe_filter.GetOutput(), reset_locators=False) 1258 self.pipeline = utils.OperationNode("probe", parents=[self, source]) 1259 self.pointdata.rename("vtkValidPointMask", "ValidPointMask") 1260 return self 1261 1262 def compute_cell_size(self): 1263 """ 1264 Add to this object a cell data array 1265 containing the area, volume and edge length 1266 of the cells (when applicable to the object type). 1267 1268 Array names are: `Area`, `Volume`, `Length`. 1269 """ 1270 csf = vtk.new("CellSizeFilter") 1271 csf.SetInputData(self.dataset) 1272 csf.SetComputeArea(1) 1273 csf.SetComputeVolume(1) 1274 csf.SetComputeLength(1) 1275 csf.SetComputeVertexCount(0) 1276 csf.SetAreaArrayName("Area") 1277 csf.SetVolumeArrayName("Volume") 1278 csf.SetLengthArrayName("Length") 1279 csf.Update() 1280 self._update(csf.GetOutput(), reset_locators=False) 1281 return self 1282 1283 def integrate_arrays_over_domain(self): 1284 """ 1285 Integrate point and cell data arrays while computing length, area or volume 1286 of the domain. It works for 1D, 2D or 3D cells. 1287 For volumetric datasets, this filter ignores all but 3D cells. 1288 It will not compute the volume contained in a closed surface. 1289 1290 Returns a dictionary with keys: `pointdata`, `celldata`, `metadata`, 1291 which contain the integration result for the corresponding attributes. 1292 1293 Examples: 1294 ```python 1295 from vedo import * 1296 surf = Sphere(res=100) 1297 surf.pointdata['scalars'] = np.ones(surf.npoints) 1298 data = surf.integrate_arrays_over_domain() 1299 print(data['pointdata']['scalars'], "is equal to 4pi", 4*np.pi) 1300 ``` 1301 1302 ```python 1303 from vedo import * 1304 1305 xcoords1 = np.arange(0, 2.2, 0.2) 1306 xcoords2 = sqrt(np.arange(0, 4.2, 0.2)) 1307 1308 ycoords = np.arange(0, 1.2, 0.2) 1309 1310 surf1 = Grid(s=(xcoords1, ycoords)).rotate_y(-45).lw(2) 1311 surf2 = Grid(s=(xcoords2, ycoords)).rotate_y(-45).lw(2) 1312 1313 surf1.pointdata['scalars'] = surf1.vertices[:,2] 1314 surf2.pointdata['scalars'] = surf2.vertices[:,2] 1315 1316 data1 = surf1.integrate_arrays_over_domain() 1317 data2 = surf2.integrate_arrays_over_domain() 1318 1319 print(data1['pointdata']['scalars'], 1320 "is equal to", 1321 data2['pointdata']['scalars'], 1322 "even if the grids are different!", 1323 "(= the volume under the surface)" 1324 ) 1325 show(surf1, surf2, N=2, axes=1).close() 1326 ``` 1327 """ 1328 vinteg = vtk.new("IntegrateAttributes") 1329 vinteg.SetInputData(self.dataset) 1330 vinteg.Update() 1331 ugrid = vedo.UnstructuredGrid(vinteg.GetOutput()) 1332 data = dict( 1333 pointdata=ugrid.pointdata.todict(), 1334 celldata=ugrid.celldata.todict(), 1335 metadata=ugrid.metadata.todict(), 1336 ) 1337 return data 1338 1339 def write(self, filename, binary=True): 1340 """Write object to file.""" 1341 out = vedo.file_io.write(self, filename, binary) 1342 out.pipeline = utils.OperationNode( 1343 "write", parents=[self], comment=filename[:15], shape="folder", c="#8a817c" 1344 ) 1345 return out 1346 1347 def tomesh(self, bounds=()): 1348 """ 1349 Extract boundary geometry from dataset (or convert data to polygonal type). 1350 1351 Two new arrays are added to the mesh: `OriginalCellIds` and `OriginalPointIds` 1352 to keep track of the original mesh elements. 1353 """ 1354 geo = vtk.new("GeometryFilter") 1355 geo.SetInputData(self.dataset) 1356 geo.SetPassThroughCellIds(1) 1357 geo.SetPassThroughPointIds(1) 1358 geo.SetOriginalCellIdsName("OriginalCellIds") 1359 geo.SetOriginalPointIdsName("OriginalPointIds") 1360 geo.SetNonlinearSubdivisionLevel(1) 1361 geo.MergingOff() 1362 if bounds: 1363 geo.SetExtent(bounds) 1364 geo.ExtentClippingOn() 1365 geo.Update() 1366 msh = vedo.mesh.Mesh(geo.GetOutput()) 1367 msh.pipeline = utils.OperationNode("tomesh", parents=[self], c="#9e2a2b") 1368 return msh 1369 1370 def shrink(self, fraction=0.8): 1371 """ 1372 Shrink the individual cells to improve visibility. 1373 1374  1375 """ 1376 sf = vtk.new("ShrinkFilter") 1377 sf.SetInputData(self.dataset) 1378 sf.SetShrinkFactor(fraction) 1379 sf.Update() 1380 self._update(sf.GetOutput()) 1381 self.pipeline = utils.OperationNode( 1382 "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b" 1383 ) 1384 return self 1385 1386 1387############################################################################### 1388class PointAlgorithms(CommonAlgorithms): 1389 """Methods for point clouds.""" 1390 1391 def __init__(self): 1392 # print('init PointAlgorithms') 1393 super().__init__() 1394 1395 self.transform = None 1396 self.point_locator = None 1397 self.cell_locator = None 1398 self.line_locator = None 1399 1400 def apply_transform(self, LT, concatenate=True, deep_copy=True): 1401 """ 1402 Apply a linear or non-linear transformation to the mesh polygonal data. 1403 1404 Example: 1405 ```python 1406 from vedo import Cube, show, settings 1407 settings.use_parallel_projection = True 1408 c1 = Cube().rotate_z(25).pos(2,1).mirror().alpha(0.5) 1409 T = c1.transform # rotate by 5 degrees, place at (2,1) 1410 c2 = Cube().c('red4').wireframe().lw(10).lighting('off') 1411 c2.apply_transform(T) 1412 show(c1, c2, "The 2 cubes should overlap!", axes=1).close() 1413 ``` 1414 1415  1416 """ 1417 if self.dataset.GetNumberOfPoints() == 0: 1418 return self 1419 1420 if isinstance(LT, LinearTransform): 1421 LT_is_linear = True 1422 tr = LT.T 1423 if LT.is_identity(): 1424 return self 1425 1426 elif isinstance(LT, (vtk.vtkMatrix4x4, vtk.vtkLinearTransform)) or utils.is_sequence(LT): 1427 LT_is_linear = True 1428 LT = LinearTransform(LT) 1429 tr = LT.T 1430 if LT.is_identity(): 1431 return self 1432 1433 elif isinstance(LT, NonLinearTransform): 1434 LT_is_linear = False 1435 tr = LT.T 1436 self.transform = LT # reset 1437 1438 elif isinstance(LT, vtk.vtkThinPlateSplineTransform): 1439 LT_is_linear = False 1440 tr = LT 1441 self.transform = NonLinearTransform(LT) # reset 1442 1443 else: 1444 vedo.logger.error(f"apply_transform(), unknown input type:\n{LT}") 1445 return self 1446 1447 ################ 1448 if LT_is_linear: 1449 if concatenate: 1450 try: 1451 # self.transform might still not be linear 1452 self.transform.concatenate(LT) 1453 except AttributeError: 1454 # in that case reset it 1455 self.transform = LinearTransform() 1456 1457 ################ 1458 if isinstance(self.dataset, vtk.vtkPolyData): 1459 tp = vtk.new("TransformPolyDataFilter") 1460 elif isinstance(self.dataset, vtk.vtkUnstructuredGrid): 1461 tp = vtk.new("TransformFilter") 1462 tp.TransformAllInputVectorsOn() 1463 # elif isinstance(self.dataset, vtk.vtkImageData): 1464 # tp = vtk.new("ImageReslice") 1465 # tp.SetInterpolationModeToCubic() 1466 # tp.SetResliceTransform(tr) 1467 else: 1468 vedo.logger.error( 1469 f"apply_transform(), unknown input type: {[self.dataset]}") 1470 return self 1471 tp.SetTransform(tr) 1472 tp.SetInputData(self.dataset) 1473 tp.Update() 1474 out = tp.GetOutput() 1475 1476 if deep_copy: 1477 self.dataset.DeepCopy(out) 1478 else: 1479 self.dataset.ShallowCopy(out) 1480 1481 # reset the locators 1482 self.point_locator = None 1483 self.cell_locator = None 1484 self.line_locator = None 1485 return self 1486 1487 def apply_transform_from_actor(self): 1488 """ 1489 Apply the current transformation of the actor to the data. 1490 Useful when manually moving an actor (eg. when pressing "a"). 1491 Returns the `LinearTransform` object. 1492 """ 1493 M = self.actor.GetMatrix() 1494 self.apply_transform(M) 1495 iden = vtk.vtkMatrix4x4() 1496 self.actor.PokeMatrix(iden) 1497 return LinearTransform(M) 1498 1499 def pos(self, x=None, y=None, z=None): 1500 """Set/Get object position.""" 1501 if x is None: # get functionality 1502 return self.transform.position 1503 1504 if z is None and y is None: # assume x is of the form (x,y,z) 1505 if len(x) == 3: 1506 x, y, z = x 1507 else: 1508 x, y = x 1509 z = 0 1510 elif z is None: # assume x,y is of the form x, y 1511 z = 0 1512 1513 q = self.transform.position 1514 delta = [x, y, z] - q 1515 if delta[0] == delta[1] == delta[2] == 0: 1516 return self 1517 LT = LinearTransform().translate(delta) 1518 return self.apply_transform(LT) 1519 1520 def shift(self, dx=0, dy=0, dz=0): 1521 """Add a vector to the current object position.""" 1522 if utils.is_sequence(dx): 1523 utils.make3d(dx) 1524 dx, dy, dz = dx 1525 if dx == dy == dz == 0: 1526 return self 1527 LT = LinearTransform().translate([dx, dy, dz]) 1528 return self.apply_transform(LT) 1529 1530 def x(self, val=None): 1531 """Set/Get object position along x axis.""" 1532 p = self.transform.position 1533 if val is None: 1534 return p[0] 1535 self.pos(val, p[1], p[2]) 1536 return self 1537 1538 def y(self, val=None): 1539 """Set/Get object position along y axis.""" 1540 p = self.transform.position 1541 if val is None: 1542 return p[1] 1543 self.pos(p[0], val, p[2]) 1544 return self 1545 1546 def z(self, val=None): 1547 """Set/Get object position along z axis.""" 1548 p = self.transform.position 1549 if val is None: 1550 return p[2] 1551 self.pos(p[0], p[1], val) 1552 return self 1553 1554 def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False): 1555 """ 1556 Rotate around an arbitrary `axis` passing through `point`. 1557 1558 Example: 1559 ```python 1560 from vedo import * 1561 c1 = Cube() 1562 c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 1563 v = vector(0.2,1,0) 1564 p = vector(1,0,0) # axis passes through this point 1565 c2.rotate(90, axis=v, point=p) 1566 l = Line(-v+p, v+p).lw(3).c('red') 1567 show(c1, l, c2, axes=1).close() 1568 ``` 1569 1570  1571 """ 1572 LT = LinearTransform() 1573 LT.rotate(angle, axis, point, rad) 1574 return self.apply_transform(LT) 1575 1576 def rotate_x(self, angle, rad=False, around=None): 1577 """ 1578 Rotate around x-axis. If angle is in radians set `rad=True`. 1579 1580 Use `around` to define a pivoting point. 1581 """ 1582 if angle == 0: 1583 return self 1584 LT = LinearTransform().rotate_x(angle, rad, around) 1585 return self.apply_transform(LT) 1586 1587 def rotate_y(self, angle, rad=False, around=None): 1588 """ 1589 Rotate around y-axis. If angle is in radians set `rad=True`. 1590 1591 Use `around` to define a pivoting point. 1592 """ 1593 if angle == 0: 1594 return self 1595 LT = LinearTransform().rotate_y(angle, rad, around) 1596 return self.apply_transform(LT) 1597 1598 def rotate_z(self, angle, rad=False, around=None): 1599 """ 1600 Rotate around z-axis. If angle is in radians set `rad=True`. 1601 1602 Use `around` to define a pivoting point. 1603 """ 1604 if angle == 0: 1605 return self 1606 LT = LinearTransform().rotate_z(angle, rad, around) 1607 return self.apply_transform(LT) 1608 1609 def reorient(self, initaxis, newaxis, rotation=0, rad=False, xyplane=False): 1610 """ 1611 Reorient the object to point to a new direction from an initial one. 1612 If `initaxis` is None, the object will be assumed in its "default" orientation. 1613 If `xyplane` is True, the object will be rotated to lie on the xy plane. 1614 1615 Use `rotation` to first rotate the object around its `initaxis`. 1616 """ 1617 q = self.transform.position 1618 LT = LinearTransform() 1619 LT.reorient(initaxis, newaxis, q, rotation, rad, xyplane) 1620 return self.apply_transform(LT) 1621 1622 def scale(self, s=None, reset=False, origin=True): 1623 """ 1624 Set/get object's scaling factor. 1625 1626 Arguments: 1627 s : (list, float) 1628 scaling factor(s). 1629 reset : (bool) 1630 if True previous scaling factors are ignored. 1631 origin : (bool) 1632 if True scaling is applied with respect to object's position, 1633 otherwise is applied respect to (0,0,0). 1634 1635 Note: 1636 use `s=(sx,sy,sz)` to scale differently in the three coordinates. 1637 """ 1638 if s is None: 1639 return np.array(self.transform.T.GetScale()) 1640 1641 if not utils.is_sequence(s): 1642 s = [s, s, s] 1643 1644 LT = LinearTransform() 1645 if reset: 1646 old_s = np.array(self.transform.T.GetScale()) 1647 LT.scale(s / old_s) 1648 else: 1649 if origin is True: 1650 LT.scale(s, origin=self.transform.position) 1651 elif origin is False: 1652 LT.scale(s, origin=False) 1653 else: 1654 LT.scale(s, origin=origin) 1655 1656 return self.apply_transform(LT) 1657 1658 1659############################################################################### 1660class VolumeAlgorithms(CommonAlgorithms): 1661 """Methods for Volume objects.""" 1662 1663 def __init__(self): 1664 super().__init__() 1665 1666 def bounds(self): 1667 """ 1668 Get the object bounds. 1669 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 1670 """ 1671 # OVERRIDE CommonAlgorithms.bounds() which is too slow 1672 return np.array(self.dataset.GetBounds()) 1673 1674 def isosurface(self, value=None, flying_edges=True): 1675 """ 1676 Return an `Mesh` isosurface extracted from the `Volume` object. 1677 1678 Set `value` as single float or list of values to draw the isosurface(s). 1679 Use flying_edges for faster results (but sometimes can interfere with `smooth()`). 1680 1681 Examples: 1682 - [isosurfaces.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces.py) 1683 1684  1685 """ 1686 scrange = self.dataset.GetScalarRange() 1687 1688 if flying_edges: 1689 cf = vtk.new("FlyingEdges3D") 1690 cf.InterpolateAttributesOn() 1691 else: 1692 cf = vtk.new("ContourFilter") 1693 cf.UseScalarTreeOn() 1694 1695 cf.SetInputData(self.dataset) 1696 cf.ComputeNormalsOn() 1697 1698 if utils.is_sequence(value): 1699 cf.SetNumberOfContours(len(value)) 1700 for i, t in enumerate(value): 1701 cf.SetValue(i, t) 1702 else: 1703 if value is None: 1704 value = (2 * scrange[0] + scrange[1]) / 3.0 1705 # print("automatic isosurface value =", value) 1706 cf.SetValue(0, value) 1707 1708 cf.Update() 1709 poly = cf.GetOutput() 1710 1711 out = vedo.mesh.Mesh(poly, c=None).phong() 1712 out.mapper.SetScalarRange(scrange[0], scrange[1]) 1713 1714 out.pipeline = utils.OperationNode( 1715 "isosurface", 1716 parents=[self], 1717 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 1718 c="#4cc9f0:#e9c46a", 1719 ) 1720 return out 1721 1722 def legosurface( 1723 self, 1724 vmin=None, 1725 vmax=None, 1726 invert=False, 1727 boundary=False, 1728 array_name="input_scalars", 1729 ): 1730 """ 1731 Represent an object - typically a `Volume` - as lego blocks (voxels). 1732 By default colors correspond to the volume's scalar. 1733 Returns an `Mesh` object. 1734 1735 Arguments: 1736 vmin : (float) 1737 the lower threshold, voxels below this value are not shown. 1738 vmax : (float) 1739 the upper threshold, voxels above this value are not shown. 1740 boundary : (bool) 1741 controls whether to include cells that are partially inside 1742 array_name : (int, str) 1743 name or index of the scalar array to be considered 1744 1745 Examples: 1746 - [legosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/legosurface.py) 1747 1748  1749 """ 1750 imp_dataset = vtk.new("ImplicitDataSet") 1751 imp_dataset.SetDataSet(self.dataset) 1752 window = vtk.new("ImplicitWindowFunction") 1753 window.SetImplicitFunction(imp_dataset) 1754 1755 srng = list(self.dataset.GetScalarRange()) 1756 if vmin is not None: 1757 srng[0] = vmin 1758 if vmax is not None: 1759 srng[1] = vmax 1760 tol = 0.00001 * (srng[1] - srng[0]) 1761 srng[0] -= tol 1762 srng[1] += tol 1763 window.SetWindowRange(srng) 1764 1765 extract = vtk.new("ExtractGeometry") 1766 extract.SetInputData(self.dataset) 1767 extract.SetImplicitFunction(window) 1768 extract.SetExtractInside(invert) 1769 extract.SetExtractBoundaryCells(boundary) 1770 extract.Update() 1771 1772 gf = vtk.new("GeometryFilter") 1773 gf.SetInputData(extract.GetOutput()) 1774 gf.Update() 1775 1776 m = vedo.mesh.Mesh(gf.GetOutput()).lw(0.1).flat() 1777 m.map_points_to_cells() 1778 m.celldata.select(array_name) 1779 1780 m.pipeline = utils.OperationNode( 1781 "legosurface", 1782 parents=[self], 1783 comment=f"array: {array_name}", 1784 c="#4cc9f0:#e9c46a", 1785 ) 1786 return m 1787 1788 def tomesh(self, fill=True, shrink=1.0): 1789 """ 1790 Build a polygonal Mesh from the current object. 1791 1792 If `fill=True`, the interior faces of all the cells are created. 1793 (setting a `shrink` value slightly smaller than the default 1.0 1794 can avoid flickering due to internal adjacent faces). 1795 1796 If `fill=False`, only the boundary faces will be generated. 1797 """ 1798 gf = vtk.new("GeometryFilter") 1799 if fill: 1800 sf = vtk.new("ShrinkFilter") 1801 sf.SetInputData(self.dataset) 1802 sf.SetShrinkFactor(shrink) 1803 sf.Update() 1804 gf.SetInputData(sf.GetOutput()) 1805 gf.Update() 1806 poly = gf.GetOutput() 1807 if shrink == 1.0: 1808 clean_poly = vtk.new("CleanPolyData") 1809 clean_poly.PointMergingOn() 1810 clean_poly.ConvertLinesToPointsOn() 1811 clean_poly.ConvertPolysToLinesOn() 1812 clean_poly.ConvertStripsToPolysOn() 1813 clean_poly.SetInputData(poly) 1814 clean_poly.Update() 1815 poly = clean_poly.GetOutput() 1816 else: 1817 gf.SetInputData(self.dataset) 1818 gf.Update() 1819 poly = gf.GetOutput() 1820 1821 msh = vedo.mesh.Mesh(poly).flat() 1822 msh.scalarbar = self.scalarbar 1823 lut = utils.ctf2lut(self) 1824 if lut: 1825 msh.mapper.SetLookupTable(lut) 1826 1827 msh.pipeline = utils.OperationNode( 1828 "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a" 1829 ) 1830 return msh
43class DataArrayHelper: 44 # Internal use only. 45 # Helper class to manage data associated to either 46 # points (or vertices) and cells (or faces). 47 def __init__(self, obj, association): 48 49 self.obj = obj 50 self.association = association 51 52 def __getitem__(self, key): 53 54 if self.association == 0: 55 data = self.obj.dataset.GetPointData() 56 57 elif self.association == 1: 58 data = self.obj.dataset.GetCellData() 59 60 elif self.association == 2: 61 data = self.obj.dataset.GetFieldData() 62 63 varr = data.GetAbstractArray(key) 64 if isinstance(varr, vtk.vtkStringArray): 65 if isinstance(key, int): 66 key = data.GetArrayName(key) 67 n = varr.GetNumberOfValues() 68 narr = [varr.GetValue(i) for i in range(n)] 69 return narr 70 ########### 71 72 else: 73 raise RuntimeError() 74 75 if isinstance(key, int): 76 key = data.GetArrayName(key) 77 78 arr = data.GetArray(key) 79 if not arr: 80 return None 81 return utils.vtk2numpy(arr) 82 83 def __setitem__(self, key, input_array): 84 85 if self.association == 0: 86 data = self.obj.dataset.GetPointData() 87 n = self.obj.dataset.GetNumberOfPoints() 88 self.obj.mapper.SetScalarModeToUsePointData() 89 90 elif self.association == 1: 91 data = self.obj.dataset.GetCellData() 92 n = self.obj.dataset.GetNumberOfCells() 93 self.obj.mapper.SetScalarModeToUseCellData() 94 95 elif self.association == 2: 96 data = self.obj.dataset.GetFieldData() 97 if not utils.is_sequence(input_array): 98 input_array = [input_array] 99 100 if isinstance(input_array[0], str): 101 varr = vtk.vtkStringArray() 102 varr.SetName(key) 103 varr.SetNumberOfComponents(1) 104 varr.SetNumberOfTuples(len(input_array)) 105 for i, iarr in enumerate(input_array): 106 if isinstance(iarr, np.ndarray): 107 iarr = iarr.tolist() # better format 108 # Note: a string k can be converted to numpy with 109 # import json; k = np.array(json.loads(k)) 110 varr.InsertValue(i, str(iarr)) 111 else: 112 try: 113 varr = utils.numpy2vtk(input_array, name=key) 114 except TypeError as e: 115 vedo.logger.error( 116 f"cannot create metadata with input object:\n" 117 f"{input_array}" 118 f"\n\nAllowed content examples are:\n" 119 f"- flat list of strings ['a','b', 1, [1,2,3], ...]" 120 f" (first item must be a string in this case)\n" 121 f" hint: use k = np.array(json.loads(k)) to convert strings\n" 122 f"- numpy arrays of any shape" 123 ) 124 raise e 125 126 data.AddArray(varr) 127 return ############ 128 129 else: 130 raise RuntimeError() 131 132 if len(input_array) != n: 133 vedo.logger.error( 134 f"Error in point/cell data: length of input {len(input_array)}" 135 f" != {n} nr. of elements" 136 ) 137 raise RuntimeError() 138 139 input_array = np.asarray(input_array) 140 varr = utils.numpy2vtk(input_array, name=key) 141 data.AddArray(varr) 142 143 if len(input_array.shape) == 1: # scalars 144 data.SetActiveScalars(key) 145 elif len(input_array.shape) == 2 and input_array.shape[1] == 3: # vectors 146 if key.lower() == "normals": 147 data.SetActiveNormals(key) 148 else: 149 data.SetActiveVectors(key) 150 151 def keys(self): 152 """Return the list of available data array names""" 153 if self.association == 0: 154 data = self.obj.dataset.GetPointData() 155 elif self.association == 1: 156 data = self.obj.dataset.GetCellData() 157 elif self.association == 2: 158 data = self.obj.dataset.GetFieldData() 159 arrnames = [] 160 for i in range(data.GetNumberOfArrays()): 161 if self.association == 2: 162 name = data.GetAbstractArray(i).GetName() 163 else: 164 name = data.GetArray(i).GetName() 165 if name: 166 arrnames.append(name) 167 return arrnames 168 169 def items(self): 170 """Return the list of available data array `(names, values)`.""" 171 if self.association == 0: 172 data = self.obj.dataset.GetPointData() 173 elif self.association == 1: 174 data = self.obj.dataset.GetCellData() 175 elif self.association == 2: 176 data = self.obj.dataset.GetFieldData() 177 arrnames = [] 178 for i in range(data.GetNumberOfArrays()): 179 if self.association == 2: 180 name = data.GetAbstractArray(i).GetName() 181 else: 182 name = data.GetArray(i).GetName() 183 if name: 184 arrnames.append((name, self[name])) 185 return arrnames 186 187 def todict(self): 188 """Return a dictionary of the available data arrays.""" 189 return dict(self.items()) 190 191 def rename(self, oldname, newname): 192 """Rename an array""" 193 if self.association == 0: 194 varr = self.obj.dataset.GetPointData().GetArray(oldname) 195 elif self.association == 1: 196 varr = self.obj.dataset.GetCellData().GetArray(oldname) 197 elif self.association == 2: 198 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 199 if varr: 200 varr.SetName(newname) 201 else: 202 vedo.logger.warning( 203 f"Cannot rename non existing array {oldname} to {newname}" 204 ) 205 206 def remove(self, key): 207 """Remove a data array by name or number""" 208 if self.association == 0: 209 self.obj.dataset.GetPointData().RemoveArray(key) 210 elif self.association == 1: 211 self.obj.dataset.GetCellData().RemoveArray(key) 212 elif self.association == 2: 213 self.obj.dataset.GetFieldData().RemoveArray(key) 214 215 def clear(self): 216 """Remove all data associated to this object""" 217 if self.association == 0: 218 data = self.obj.dataset.GetPointData() 219 elif self.association == 1: 220 data = self.obj.dataset.GetCellData() 221 elif self.association == 2: 222 data = self.obj.dataset.GetFieldData() 223 for i in range(data.GetNumberOfArrays()): 224 if self.association == 2: 225 name = data.GetAbstractArray(i).GetName() 226 else: 227 name = data.GetArray(i).GetName() 228 data.RemoveArray(name) 229 230 def select(self, key): 231 """Select one specific array by its name to make it the `active` one.""" 232 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 233 # and NOT mapped through the lookup table, while everything else is. 234 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 235 # types are treated as colors with values in the range 0-255 236 # and floating types are treated as colors with values in the range 0.0-1.0. 237 # Setting ColorModeToMapScalars means that all scalar data will be mapped 238 # through the lookup table. 239 # (Note that for multi-component scalars, the particular component 240 # to use for mapping can be specified using the SelectColorArray() method.) 241 if self.association == 0: 242 data = self.obj.dataset.GetPointData() 243 self.obj.mapper.SetScalarModeToUsePointData() 244 else: 245 data = self.obj.dataset.GetCellData() 246 self.obj.mapper.SetScalarModeToUseCellData() 247 248 if isinstance(key, int): 249 key = data.GetArrayName(key) 250 251 arr = data.GetArray(key) 252 if not arr: 253 return self.obj 254 255 nc = arr.GetNumberOfComponents() 256 # print("GetNumberOfComponents", nc) 257 if nc == 1: 258 data.SetActiveScalars(key) 259 elif nc == 2: 260 data.SetTCoords(arr) 261 elif nc == 3 or nc == 4: 262 if "rgb" in key.lower(): 263 data.SetActiveScalars(key) 264 try: 265 # could be a volume mapper 266 self.obj.mapper.SetColorModeToDirectScalars() 267 except AttributeError: 268 pass 269 else: 270 data.SetActiveVectors(key) 271 elif nc == 9: 272 data.SetActiveTensors(key) 273 else: 274 vedo.logger.error(f"Cannot select array {key} with {nc} components") 275 return self.obj 276 277 try: 278 # could be a volume mapper 279 self.obj.mapper.SetArrayName(key) 280 self.obj.mapper.ScalarVisibilityOn() 281 except AttributeError: 282 pass 283 284 return self.obj 285 286 def select_texture_coords(self, key): 287 """Select one specific array to be used as texture coordinates.""" 288 if self.association == 0: 289 data = self.obj.dataset.GetPointData() 290 else: 291 vedo.logger.warning("texture coordinates are only available for point data") 292 return 293 294 if isinstance(key, int): 295 key = data.GetArrayName(key) 296 data.SetTCoords(data.GetArray(key)) 297 return self.obj 298 299 def select_normals(self, key): 300 """Select one specific normal array by its name to make it the "active" one.""" 301 if self.association == 0: 302 data = self.obj.dataset.GetPointData() 303 self.obj.mapper.SetScalarModeToUsePointData() 304 else: 305 data = self.obj.dataset.GetCellData() 306 self.obj.mapper.SetScalarModeToUseCellData() 307 308 if isinstance(key, int): 309 key = data.GetArrayName(key) 310 data.SetActiveNormals(key) 311 return self.obj 312 313 def print(self, **kwargs): 314 """Print the array names available to terminal""" 315 colors.printc(self.keys(), **kwargs) 316 317 def __repr__(self) -> str: 318 """Representation""" 319 320 def _get_str(pd, header): 321 out = f"\x1b[2m\x1b[1m\x1b[7m{header}" 322 if pd.GetNumberOfArrays(): 323 if self.obj.name: 324 out += f" in {self.obj.name}" 325 out += f" contains {pd.GetNumberOfArrays()} array(s)\x1b[0m" 326 for i in range(pd.GetNumberOfArrays()): 327 varr = pd.GetArray(i) 328 out += f"\n\x1b[1m\x1b[4mArray name : {varr.GetName()}\x1b[0m" 329 out += "\nindex".ljust(15) + f": {i}" 330 t = varr.GetDataType() 331 if t in vedo.utils.array_types: 332 out += "\ntype".ljust(15) 333 out += f": {vedo.utils.array_types[t][1]} ({vedo.utils.array_types[t][0]})" 334 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 335 out += "\nshape".ljust(15) + f": {shape}" 336 out += "\nrange".ljust(15) + f": {np.array(varr.GetRange())}" 337 out += "\nmax id".ljust(15) + f": {varr.GetMaxId()}" 338 out += "\nlook up table".ljust(15) + f": {bool(varr.GetLookupTable())}" 339 out += "\nin-memory size".ljust(15) + f": {varr.GetActualMemorySize()} KB" 340 else: 341 out += " is empty.\x1b[0m" 342 return out 343 344 if self.association == 0: 345 out = _get_str(self.obj.dataset.GetPointData(), "Point Data") 346 elif self.association == 1: 347 out = _get_str(self.obj.dataset.GetCellData(), "Cell Data") 348 elif self.association == 2: 349 pd = self.obj.dataset.GetFieldData() 350 if pd.GetNumberOfArrays(): 351 out = "\x1b[2m\x1b[1m\x1b[7mMeta Data" 352 if self.obj.name: 353 out += f" in {self.obj.name}" 354 out += f" contains {pd.GetNumberOfArrays()} entries\x1b[0m" 355 for i in range(pd.GetNumberOfArrays()): 356 varr = pd.GetAbstractArray(i) 357 out += f"\n\x1b[1m\x1b[4mEntry name : {varr.GetName()}\x1b[0m" 358 out += "\nindex".ljust(15) + f": {i}" 359 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 360 out += "\nshape".ljust(15) + f": {shape}" 361 362 return out
151 def keys(self): 152 """Return the list of available data array names""" 153 if self.association == 0: 154 data = self.obj.dataset.GetPointData() 155 elif self.association == 1: 156 data = self.obj.dataset.GetCellData() 157 elif self.association == 2: 158 data = self.obj.dataset.GetFieldData() 159 arrnames = [] 160 for i in range(data.GetNumberOfArrays()): 161 if self.association == 2: 162 name = data.GetAbstractArray(i).GetName() 163 else: 164 name = data.GetArray(i).GetName() 165 if name: 166 arrnames.append(name) 167 return arrnames
Return the list of available data array names
169 def items(self): 170 """Return the list of available data array `(names, values)`.""" 171 if self.association == 0: 172 data = self.obj.dataset.GetPointData() 173 elif self.association == 1: 174 data = self.obj.dataset.GetCellData() 175 elif self.association == 2: 176 data = self.obj.dataset.GetFieldData() 177 arrnames = [] 178 for i in range(data.GetNumberOfArrays()): 179 if self.association == 2: 180 name = data.GetAbstractArray(i).GetName() 181 else: 182 name = data.GetArray(i).GetName() 183 if name: 184 arrnames.append((name, self[name])) 185 return arrnames
Return the list of available data array (names, values)
.
187 def todict(self): 188 """Return a dictionary of the available data arrays.""" 189 return dict(self.items())
Return a dictionary of the available data arrays.
191 def rename(self, oldname, newname): 192 """Rename an array""" 193 if self.association == 0: 194 varr = self.obj.dataset.GetPointData().GetArray(oldname) 195 elif self.association == 1: 196 varr = self.obj.dataset.GetCellData().GetArray(oldname) 197 elif self.association == 2: 198 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 199 if varr: 200 varr.SetName(newname) 201 else: 202 vedo.logger.warning( 203 f"Cannot rename non existing array {oldname} to {newname}" 204 )
Rename an array
206 def remove(self, key): 207 """Remove a data array by name or number""" 208 if self.association == 0: 209 self.obj.dataset.GetPointData().RemoveArray(key) 210 elif self.association == 1: 211 self.obj.dataset.GetCellData().RemoveArray(key) 212 elif self.association == 2: 213 self.obj.dataset.GetFieldData().RemoveArray(key)
Remove a data array by name or number
215 def clear(self): 216 """Remove all data associated to this object""" 217 if self.association == 0: 218 data = self.obj.dataset.GetPointData() 219 elif self.association == 1: 220 data = self.obj.dataset.GetCellData() 221 elif self.association == 2: 222 data = self.obj.dataset.GetFieldData() 223 for i in range(data.GetNumberOfArrays()): 224 if self.association == 2: 225 name = data.GetAbstractArray(i).GetName() 226 else: 227 name = data.GetArray(i).GetName() 228 data.RemoveArray(name)
Remove all data associated to this object
230 def select(self, key): 231 """Select one specific array by its name to make it the `active` one.""" 232 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 233 # and NOT mapped through the lookup table, while everything else is. 234 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 235 # types are treated as colors with values in the range 0-255 236 # and floating types are treated as colors with values in the range 0.0-1.0. 237 # Setting ColorModeToMapScalars means that all scalar data will be mapped 238 # through the lookup table. 239 # (Note that for multi-component scalars, the particular component 240 # to use for mapping can be specified using the SelectColorArray() method.) 241 if self.association == 0: 242 data = self.obj.dataset.GetPointData() 243 self.obj.mapper.SetScalarModeToUsePointData() 244 else: 245 data = self.obj.dataset.GetCellData() 246 self.obj.mapper.SetScalarModeToUseCellData() 247 248 if isinstance(key, int): 249 key = data.GetArrayName(key) 250 251 arr = data.GetArray(key) 252 if not arr: 253 return self.obj 254 255 nc = arr.GetNumberOfComponents() 256 # print("GetNumberOfComponents", nc) 257 if nc == 1: 258 data.SetActiveScalars(key) 259 elif nc == 2: 260 data.SetTCoords(arr) 261 elif nc == 3 or nc == 4: 262 if "rgb" in key.lower(): 263 data.SetActiveScalars(key) 264 try: 265 # could be a volume mapper 266 self.obj.mapper.SetColorModeToDirectScalars() 267 except AttributeError: 268 pass 269 else: 270 data.SetActiveVectors(key) 271 elif nc == 9: 272 data.SetActiveTensors(key) 273 else: 274 vedo.logger.error(f"Cannot select array {key} with {nc} components") 275 return self.obj 276 277 try: 278 # could be a volume mapper 279 self.obj.mapper.SetArrayName(key) 280 self.obj.mapper.ScalarVisibilityOn() 281 except AttributeError: 282 pass 283 284 return self.obj
Select one specific array by its name to make it the active
one.
286 def select_texture_coords(self, key): 287 """Select one specific array to be used as texture coordinates.""" 288 if self.association == 0: 289 data = self.obj.dataset.GetPointData() 290 else: 291 vedo.logger.warning("texture coordinates are only available for point data") 292 return 293 294 if isinstance(key, int): 295 key = data.GetArrayName(key) 296 data.SetTCoords(data.GetArray(key)) 297 return self.obj
Select one specific array to be used as texture coordinates.
299 def select_normals(self, key): 300 """Select one specific normal array by its name to make it the "active" one.""" 301 if self.association == 0: 302 data = self.obj.dataset.GetPointData() 303 self.obj.mapper.SetScalarModeToUsePointData() 304 else: 305 data = self.obj.dataset.GetCellData() 306 self.obj.mapper.SetScalarModeToUseCellData() 307 308 if isinstance(key, int): 309 key = data.GetArrayName(key) 310 data.SetActiveNormals(key) 311 return self.obj
Select one specific normal array by its name to make it the "active" one.
366class CommonAlgorithms: 367 """Common algorithms.""" 368 369 def __init__(self): 370 # print("init CommonAlgorithms") 371 self.dataset = None 372 self.pipeline = None 373 self.name = "" 374 self.filename = "" 375 self.time = 0 376 377 378 @property 379 def pointdata(self): 380 """ 381 Create and/or return a `numpy.array` associated to points (vertices). 382 A data array can be indexed either as a string or by an integer number. 383 E.g.: `myobj.pointdata["arrayname"]` 384 385 Usage: 386 387 `myobj.pointdata.keys()` to return the available data array names 388 389 `myobj.pointdata.select(name)` to make this array the active one 390 391 `myobj.pointdata.remove(name)` to remove this array 392 """ 393 return DataArrayHelper(self, 0) 394 395 @property 396 def celldata(self): 397 """ 398 Create and/or return a `numpy.array` associated to cells (faces). 399 A data array can be indexed either as a string or by an integer number. 400 E.g.: `myobj.celldata["arrayname"]` 401 402 Usage: 403 404 `myobj.celldata.keys()` to return the available data array names 405 406 `myobj.celldata.select(name)` to make this array the active one 407 408 `myobj.celldata.remove(name)` to remove this array 409 """ 410 return DataArrayHelper(self, 1) 411 412 @property 413 def metadata(self): 414 """ 415 Create and/or return a `numpy.array` associated to neither cells nor faces. 416 A data array can be indexed either as a string or by an integer number. 417 E.g.: `myobj.metadata["arrayname"]` 418 419 Usage: 420 421 `myobj.metadata.keys()` to return the available data array names 422 423 `myobj.metadata.select(name)` to make this array the active one 424 425 `myobj.metadata.remove(name)` to remove this array 426 """ 427 return DataArrayHelper(self, 2) 428 429 def memory_address(self): 430 """ 431 Return a unique memory address integer which may serve as the ID of the 432 object, or passed to c++ code. 433 """ 434 # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/ 435 # https://github.com/tfmoraes/polydata_connectivity 436 return int(self.dataset.GetAddressAsString("")[5:], 16) 437 438 def memory_size(self): 439 """Return the size in bytes of the object in memory.""" 440 return self.dataset.GetActualMemorySize() 441 442 def modified(self): 443 """Use in conjunction with `tonumpy()` to update any modifications to the image array.""" 444 self.dataset.GetPointData().Modified() 445 self.dataset.GetPointData().GetScalars().Modified() 446 return self 447 448 def box(self, scale=1, padding=0): 449 """ 450 Return the bounding box as a new `Mesh` object. 451 452 Arguments: 453 scale : (float) 454 box size can be scaled by a factor 455 padding : (float, list) 456 a constant padding can be added (can be a list `[padx,pady,padz]`) 457 """ 458 b = self.bounds() 459 if not utils.is_sequence(padding): 460 padding = [padding, padding, padding] 461 length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4] 462 tol = (length + width + height) / 30000 # useful for boxing text 463 pos = [(b[0] + b[1])/2, (b[3] + b[2])/2, (b[5] + b[4])/2 - tol] 464 bx = vedo.shapes.Box( 465 pos, 466 length * scale + padding[0], 467 width * scale + padding[1], 468 height * scale + padding[2], 469 c="gray", 470 ) 471 try: 472 pr = vtk.vtkProperty() 473 pr.DeepCopy(self.properties) 474 bx.SetProperty(pr) 475 bx.properties = pr 476 except (AttributeError, TypeError): 477 pass 478 bx.flat().lighting("off").wireframe(True) 479 return bx 480 481 def bounds(self): 482 """ 483 Get the object bounds. 484 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 485 """ 486 try: # this is very slow for large meshes 487 pts = self.vertices 488 xmin, ymin, zmin = np.min(pts, axis=0) 489 xmax, ymax, zmax = np.max(pts, axis=0) 490 return np.array([xmin, xmax, ymin, ymax, zmin, zmax]) 491 except (AttributeError, ValueError): 492 return np.array(self.dataset.GetBounds()) 493 494 def xbounds(self, i=None): 495 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 496 b = self.bounds() 497 if i is not None: 498 return b[i] 499 return np.array([b[0], b[1]]) 500 501 def ybounds(self, i=None): 502 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 503 b = self.bounds() 504 if i == 0: 505 return b[2] 506 if i == 1: 507 return b[3] 508 return np.array([b[2], b[3]]) 509 510 def zbounds(self, i=None): 511 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 512 b = self.bounds() 513 if i == 0: 514 return b[4] 515 if i == 1: 516 return b[5] 517 return np.array([b[4], b[5]]) 518 519 def diagonal_size(self): 520 """Get the length of the diagonal of the bounding box.""" 521 b = self.bounds() 522 return np.sqrt((b[1] - b[0])**2 + (b[3] - b[2])**2 + (b[5] - b[4])**2) 523 524 def average_size(self): 525 """ 526 Calculate and return the average size of the object. 527 This is the mean of the vertex distances from the center of mass. 528 """ 529 coords = self.vertices 530 cm = np.mean(coords, axis=0) 531 if coords.shape[0] == 0: 532 return 0.0 533 cc = coords - cm 534 return np.mean(np.linalg.norm(cc, axis=1)) 535 536 def center_of_mass(self): 537 """Get the center of mass of the object.""" 538 cmf = vtk.new("CenterOfMass") 539 cmf.SetInputData(self.dataset) 540 cmf.Update() 541 c = cmf.GetCenter() 542 return np.array(c) 543 544 def copy_data_from(self, obj): 545 """Copy all data (point and cell data) from this input object""" 546 self.dataset.GetPointData().PassData(obj.dataset.GetPointData()) 547 self.dataset.GetCellData().PassData(obj.dataset.GetCellData()) 548 self.pipeline = utils.OperationNode( 549 "copy_data_from", 550 parents=[self, obj], 551 comment=f"{obj.__class__.__name__}", 552 shape="note", 553 c="#ccc5b9", 554 ) 555 return self 556 557 def inputdata(self): 558 """Obsolete, use `.dataset` instead.""" 559 colors.printc("WARNING: 'inputdata()' is obsolete, use '.dataset' instead.", c="y") 560 return self.dataset 561 562 @property 563 def npoints(self): 564 """Retrieve the number of points (or vertices).""" 565 return self.dataset.GetNumberOfPoints() 566 567 @property 568 def nvertices(self): 569 """Retrieve the number of vertices (or points).""" 570 return self.dataset.GetNumberOfPoints() 571 572 @property 573 def ncells(self): 574 """Retrieve the number of cells.""" 575 return self.dataset.GetNumberOfCells() 576 577 def points(self, pts=None): 578 """Obsolete, use `self.vertices` or `self.coordinates` instead.""" 579 if pts is None: ### getter 580 581 if warnings["points_getter"]: 582 colors.printc(warnings["points_getter"], c="y") 583 warnings["points_getter"] = "" 584 return self.vertices 585 586 else: ### setter 587 588 if warnings["points_setter"]: 589 colors.printc(warnings["points_setter"], c="y") 590 warnings["points_setter"] = "" 591 592 pts = np.asarray(pts, dtype=np.float32) 593 594 if pts.ndim == 1: 595 ### getter by point index ################### 596 indices = pts.astype(int) 597 vpts = self.dataset.GetPoints() 598 arr = utils.vtk2numpy(vpts.GetData()) 599 return arr[indices] ########### 600 601 ### setter #################################### 602 if pts.shape[1] == 2: 603 pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)] 604 arr = utils.numpy2vtk(pts, dtype=np.float32) 605 606 vpts = self.dataset.GetPoints() 607 vpts.SetData(arr) 608 vpts.Modified() 609 # reset mesh to identity matrix position/rotation: 610 self.point_locator = None 611 self.cell_locator = None 612 self.line_locator = None 613 self.transform = LinearTransform() 614 return self 615 616 @property 617 def cell_centers(self): 618 """ 619 Get the coordinates of the cell centers. 620 621 Examples: 622 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 623 """ 624 vcen = vtk.new("CellCenters") 625 vcen.SetInputData(self.dataset) 626 vcen.Update() 627 return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData()) 628 629 @property 630 def lines(self): 631 """ 632 Get lines connectivity ids as a python array 633 formatted as `[[id0,id1], [id3,id4], ...]` 634 635 See also: `lines_as_flat_array()`. 636 """ 637 # Get cell connettivity ids as a 1D array. The vtk format is: 638 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 639 arr1d = utils.vtk2numpy(self.dataset.GetLines().GetData()) 640 i = 0 641 conn = [] 642 n = len(arr1d) 643 for _ in range(n): 644 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 645 conn.append(cell) 646 i += arr1d[i] + 1 647 if i >= n: 648 break 649 650 return conn # cannot always make a numpy array of it! 651 652 @property 653 def lines_as_flat_array(self): 654 """ 655 Get lines connectivity ids as a 1D numpy array. 656 Format is e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 657 658 See also: `lines()`. 659 """ 660 return utils.vtk2numpy(self.dataset.GetLines().GetData()) 661 662 def mark_boundaries(self): 663 """ 664 Mark cells and vertices if they lie on a boundary. 665 A new array called `BoundaryCells` is added to the object. 666 """ 667 mb = vtk.new("MarkBoundaryFilter") 668 mb.SetInputData(self.dataset) 669 mb.Update() 670 self.dataset.DeepCopy(mb.GetOutput()) 671 self.pipeline = utils.OperationNode("mark_boundaries", parents=[self]) 672 return self 673 674 def find_cells_in_bounds(self, xbounds=(), ybounds=(), zbounds=()): 675 """ 676 Find cells that are within the specified bounds. 677 """ 678 try: 679 xbounds = list(xbounds.bounds()) 680 except AttributeError: 681 pass 682 683 if len(xbounds) == 6: 684 bnds = xbounds 685 else: 686 bnds = list(self.bounds()) 687 if len(xbounds) == 2: 688 bnds[0] = xbounds[0] 689 bnds[1] = xbounds[1] 690 if len(ybounds) == 2: 691 bnds[2] = ybounds[0] 692 bnds[3] = ybounds[1] 693 if len(zbounds) == 2: 694 bnds[4] = zbounds[0] 695 bnds[5] = zbounds[1] 696 697 cell_ids = vtk.vtkIdList() 698 if not self.cell_locator: 699 self.cell_locator = vtk.new("CellTreeLocator") 700 self.cell_locator.SetDataSet(self.dataset) 701 self.cell_locator.BuildLocator() 702 self.cell_locator.FindCellsWithinBounds(bnds, cell_ids) 703 cids = [] 704 for i in range(cell_ids.GetNumberOfIds()): 705 cid = cell_ids.GetId(i) 706 cids.append(cid) 707 return np.array(cids) 708 709 def find_cells_along_line(self, p0, p1, tol=0.001): 710 """ 711 Find cells that are intersected by a line segment. 712 """ 713 cell_ids = vtk.vtkIdList() 714 if not self.cell_locator: 715 self.cell_locator = vtk.new("CellTreeLocator") 716 self.cell_locator.SetDataSet(self.dataset) 717 self.cell_locator.BuildLocator() 718 self.cell_locator.FindCellsAlongLine(p0, p1, tol, cell_ids) 719 cids = [] 720 for i in range(cell_ids.GetNumberOfIds()): 721 cid = cell_ids.GetId(i) 722 cids.append(cid) 723 return np.array(cids) 724 725 def find_cells_along_plane(self, origin, normal, tol=0.001): 726 """ 727 Find cells that are intersected by a plane. 728 """ 729 cell_ids = vtk.vtkIdList() 730 if not self.cell_locator: 731 self.cell_locator = vtk.new("CellTreeLocator") 732 self.cell_locator.SetDataSet(self.dataset) 733 self.cell_locator.BuildLocator() 734 self.cell_locator.FindCellsAlongPlane(origin, normal, tol, cell_ids) 735 cids = [] 736 for i in range(cell_ids.GetNumberOfIds()): 737 cid = cell_ids.GetId(i) 738 cids.append(cid) 739 return np.array(cids) 740 741 def delete_cells_by_point_index(self, indices): 742 """ 743 Delete a list of vertices identified by any of their vertex index. 744 745 See also `delete_cells()`. 746 747 Examples: 748 - [delete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delete_mesh_pts.py) 749 750  751 """ 752 cell_ids = vtk.vtkIdList() 753 self.dataset.BuildLinks() 754 n = 0 755 for i in np.unique(indices): 756 self.dataset.GetPointCells(i, cell_ids) 757 for j in range(cell_ids.GetNumberOfIds()): 758 self.dataset.DeleteCell(cell_ids.GetId(j)) # flag cell 759 n += 1 760 761 self.dataset.RemoveDeletedCells() 762 self.dataset.Modified() 763 self.pipeline = utils.OperationNode( 764 "delete_cells_by_point_index", parents=[self]) 765 return self 766 767 def map_cells_to_points(self, arrays=(), move=False): 768 """ 769 Interpolate cell data (i.e., data specified per cell or face) 770 into point data (i.e., data specified at each vertex). 771 The method of transformation is based on averaging the data values 772 of all cells using a particular point. 773 774 A custom list of arrays to be mapped can be passed in input. 775 776 Set `move=True` to delete the original `celldata` array. 777 """ 778 c2p = vtk.new("CellDataToPointData") 779 c2p.SetInputData(self.dataset) 780 if not move: 781 c2p.PassCellDataOn() 782 if arrays: 783 c2p.ClearCellDataArrays() 784 c2p.ProcessAllArraysOff() 785 for arr in arrays: 786 c2p.AddCellDataArray(arr) 787 else: 788 c2p.ProcessAllArraysOn() 789 c2p.Update() 790 self._update(c2p.GetOutput(), reset_locators=False) 791 self.mapper.SetScalarModeToUsePointData() 792 self.pipeline = utils.OperationNode("map_cells_to_points", parents=[self]) 793 return self 794 795 @property 796 def vertices(self): 797 """Return the vertices (points) coordinates.""" 798 try: 799 # valid for polydata and unstructured grid 800 varr = self.dataset.GetPoints().GetData() 801 802 except AttributeError: 803 try: 804 # valid for rectilinear/structured grid, image data 805 v2p = vtk.new("ImageToPoints") 806 v2p.SetInputData(self.dataset) 807 v2p.Update() 808 varr = v2p.GetOutput().GetPoints().GetData() 809 except AttributeError: 810 return np.array([]) 811 812 narr = utils.vtk2numpy(varr) 813 return narr 814 815 # setter 816 @vertices.setter 817 def vertices(self, pts): 818 """Set vertices (points) coordinates.""" 819 pts = utils.make3d(pts) 820 arr = utils.numpy2vtk(pts, dtype=np.float32) 821 try: 822 vpts = self.dataset.GetPoints() 823 vpts.SetData(arr) 824 vpts.Modified() 825 except AttributeError: 826 vedo.logger.error(f"Cannot set vertices for object {type(self)}") 827 return self 828 # reset mesh to identity matrix position/rotation: 829 self.point_locator = None 830 self.cell_locator = None 831 self.line_locator = None 832 self.transform = LinearTransform() 833 return self 834 835 836 @property 837 def coordinates(self): 838 """Return the vertices (points) coordinates. Same as `vertices`.""" 839 return self.vertices 840 841 @coordinates.setter 842 def coordinates(self, pts): 843 """Set vertices (points) coordinates. Same as `vertices`.""" 844 self.vertices = pts 845 846 @property 847 def cells_as_flat_array(self): 848 """ 849 Get cell connectivity ids as a 1D numpy array. 850 Format is e.g. [3, 10,20,30 4, 10,11,12,13 ...] 851 """ 852 try: 853 # valid for unstructured grid 854 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 855 except AttributeError: 856 # valid for polydata 857 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 858 # if arr1d.size == 0: 859 # arr1d = utils.vtk2numpy(self.dataset.GetStrips().GetData()) 860 return arr1d 861 862 @property 863 def cells(self): 864 """ 865 Get the cells connectivity ids as a numpy array. 866 867 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 868 """ 869 try: 870 # valid for unstructured grid 871 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 872 except AttributeError: 873 # valid for polydata 874 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 875 # if arr1d.size == 0: 876 # arr1d = utils.vtk2numpy(self.dataset.GetStrips().GetData()) 877 878 # Get cell connettivity ids as a 1D array. vtk format is: 879 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 880 i = 0 881 conn = [] 882 n = len(arr1d) 883 if n: 884 while True: 885 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 886 conn.append(cell) 887 i += arr1d[i] + 1 888 if i >= n: 889 break 890 return conn 891 892 def map_points_to_cells(self, arrays=(), move=False): 893 """ 894 Interpolate point data (i.e., data specified per point or vertex) 895 into cell data (i.e., data specified per cell). 896 The method of transformation is based on averaging the data values 897 of all points defining a particular cell. 898 899 A custom list of arrays to be mapped can be passed in input. 900 901 Set `move=True` to delete the original `pointdata` array. 902 903 Examples: 904 - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py) 905 """ 906 p2c = vtk.new("PointDataToCellData") 907 p2c.SetInputData(self.dataset) 908 if not move: 909 p2c.PassPointDataOn() 910 if arrays: 911 p2c.ClearPointDataArrays() 912 p2c.ProcessAllArraysOff() 913 for arr in arrays: 914 p2c.AddPointDataArray(arr) 915 else: 916 p2c.ProcessAllArraysOn() 917 p2c.Update() 918 self._update(p2c.GetOutput(), reset_locators=False) 919 self.mapper.SetScalarModeToUseCellData() 920 self.pipeline = utils.OperationNode("map_points_to_cells", parents=[self]) 921 return self 922 923 def resample_data_from(self, source, tol=None, categorical=False): 924 """ 925 Resample point and cell data from another dataset. 926 The output has the same structure but its point data have 927 the resampled values from target. 928 929 Use `tol` to set the tolerance used to compute whether 930 a point in the source is in a cell of the current object. 931 Points without resampled values, and their cells, are marked as blank. 932 If the data is categorical, then the resulting data will be determined 933 by a nearest neighbor interpolation scheme. 934 935 Example: 936 ```python 937 from vedo import * 938 m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1) 939 pts = m1.vertices 940 ces = m1.cell_centers 941 m1.pointdata["xvalues"] = np.power(pts[:,0], 3) 942 m1.celldata["yvalues"] = np.power(ces[:,1], 3) 943 m2 = Mesh(dataurl+'bunny.obj') 944 m2.resample_arrays_from(m1) 945 # print(m2.pointdata["xvalues"]) 946 show(m1, m2 , N=2, axes=1) 947 ``` 948 """ 949 rs = vtk.new("ResampleWithDataSet") 950 rs.SetInputData(self.dataset) 951 rs.SetSourceData(source.dataset) 952 953 rs.SetPassPointArrays(True) 954 rs.SetPassCellArrays(True) 955 rs.SetPassFieldArrays(True) 956 rs.SetCategoricalData(categorical) 957 958 rs.SetComputeTolerance(True) 959 if tol: 960 rs.SetComputeTolerance(False) 961 rs.SetTolerance(tol) 962 rs.Update() 963 self._update(rs.GetOutput(), reset_locators=False) 964 self.pipeline = utils.OperationNode( 965 "resample_data_from", 966 comment=f"{source.__class__.__name__}", 967 parents=[self, source] 968 ) 969 return self 970 971 def interpolate_data_from( 972 self, 973 source, 974 radius=None, 975 n=None, 976 kernel="shepard", 977 exclude=("Normals",), 978 on="points", 979 null_strategy=1, 980 null_value=0, 981 ): 982 """ 983 Interpolate over source to port its data onto the current object using various kernels. 984 985 If n (number of closest points to use) is set then radius value is ignored. 986 987 Check out also: 988 `probe()` which in many cases can be faster. 989 990 Arguments: 991 kernel : (str) 992 available kernels are [shepard, gaussian, linear] 993 null_strategy : (int) 994 specify a strategy to use when encountering a "null" point 995 during the interpolation process. Null points occur when the local neighborhood 996 (of nearby points to interpolate from) is empty. 997 998 - Case 0: an output array is created that marks points 999 as being valid (=1) or null (invalid =0), and the null_value is set as well 1000 - Case 1: the output data value(s) are set to the provided null_value 1001 - Case 2: simply use the closest point to perform the interpolation. 1002 null_value : (float) 1003 see above. 1004 1005 Examples: 1006 - [interpolate_scalar1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar1.py) 1007 - [interpolate_scalar3.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar3.py) 1008 - [interpolate_scalar4.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar4.py) 1009 - [image_probe.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/image_probe.py) 1010 1011  1012 """ 1013 if radius is None and not n: 1014 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 1015 raise RuntimeError 1016 1017 if on == "points": 1018 points = source.dataset 1019 elif on == "cells": 1020 c2p = vtk.new("CellDataToPointData") 1021 c2p.SetInputData(source.dataset) 1022 c2p.Update() 1023 points = c2p.GetOutput() 1024 else: 1025 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 1026 raise RuntimeError() 1027 1028 locator = vtk.new("PointLocator") 1029 locator.SetDataSet(points) 1030 locator.BuildLocator() 1031 1032 if kernel.lower() == "shepard": 1033 kern = vtk.new("ShepardKernel") 1034 kern.SetPowerParameter(2) 1035 elif kernel.lower() == "gaussian": 1036 kern = vtk.new("GaussianKernel") 1037 kern.SetSharpness(2) 1038 elif kernel.lower() == "linear": 1039 kern = vtk.new("LinearKernel") 1040 else: 1041 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 1042 raise RuntimeError() 1043 1044 if n: 1045 kern.SetNumberOfPoints(n) 1046 kern.SetKernelFootprintToNClosest() 1047 else: 1048 kern.SetRadius(radius) 1049 kern.SetKernelFootprintToRadius() 1050 1051 interpolator = vtk.new("PointInterpolator") 1052 interpolator.SetInputData(self.dataset) 1053 interpolator.SetSourceData(points) 1054 interpolator.SetKernel(kern) 1055 interpolator.SetLocator(locator) 1056 interpolator.PassFieldArraysOff() 1057 interpolator.SetNullPointsStrategy(null_strategy) 1058 interpolator.SetNullValue(null_value) 1059 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 1060 for ex in exclude: 1061 interpolator.AddExcludedArray(ex) 1062 interpolator.Update() 1063 1064 if on == "cells": 1065 p2c = vtk.new("PointDataToCellData") 1066 p2c.SetInputData(interpolator.GetOutput()) 1067 p2c.Update() 1068 cpoly = p2c.GetOutput() 1069 else: 1070 cpoly = interpolator.GetOutput() 1071 1072 self._update(cpoly, reset_locators=False) 1073 1074 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 1075 return self 1076 1077 def add_ids(self): 1078 """ 1079 Generate point and cell ids arrays. 1080 1081 Two new arrays are added to the mesh: `PointID` and `CellID`. 1082 """ 1083 ids = vtk.new("IdFilter") 1084 ids.SetInputData(self.dataset) 1085 ids.PointIdsOn() 1086 ids.CellIdsOn() 1087 ids.FieldDataOff() 1088 ids.SetPointIdsArrayName("PointID") 1089 ids.SetCellIdsArrayName("CellID") 1090 ids.Update() 1091 self._update(ids.GetOutput(), reset_locators=False) 1092 self.pipeline = utils.OperationNode("add_ids", parents=[self]) 1093 return self 1094 1095 def gradient(self, input_array=None, on="points", fast=False): 1096 """ 1097 Compute and return the gradiend of the active scalar field as a numpy array. 1098 1099 Arguments: 1100 input_array : (str) 1101 array of the scalars to compute the gradient, 1102 if None the current active array is selected 1103 on : (str) 1104 compute either on 'points' or 'cells' data 1105 fast : (bool) 1106 if True, will use a less accurate algorithm 1107 that performs fewer derivative calculations (and is therefore faster). 1108 1109 Examples: 1110 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py) 1111 1112  1113 """ 1114 gra = vtk.new("GradientFilter") 1115 if on.startswith("p"): 1116 varr = self.dataset.GetPointData() 1117 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1118 elif on.startswith("c"): 1119 varr = self.dataset.GetCellData() 1120 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1121 else: 1122 vedo.logger.error(f"in gradient: unknown option {on}") 1123 raise RuntimeError 1124 1125 if input_array is None: 1126 if varr.GetScalars(): 1127 input_array = varr.GetScalars().GetName() 1128 else: 1129 vedo.logger.error(f"in gradient: no scalars found for {on}") 1130 raise RuntimeError 1131 1132 gra.SetInputData(self.dataset) 1133 gra.SetInputScalars(tp, input_array) 1134 gra.SetResultArrayName("Gradient") 1135 gra.SetFasterApproximation(fast) 1136 gra.ComputeDivergenceOff() 1137 gra.ComputeVorticityOff() 1138 gra.ComputeGradientOn() 1139 gra.Update() 1140 if on.startswith("p"): 1141 gvecs = utils.vtk2numpy(gra.GetOutput().GetPointData().GetArray("Gradient")) 1142 else: 1143 gvecs = utils.vtk2numpy(gra.GetOutput().GetCellData().GetArray("Gradient")) 1144 return gvecs 1145 1146 def divergence(self, array_name=None, on="points", fast=False): 1147 """ 1148 Compute and return the divergence of a vector field as a numpy array. 1149 1150 Arguments: 1151 array_name : (str) 1152 name of the array of vectors to compute the divergence, 1153 if None the current active array is selected 1154 on : (str) 1155 compute either on 'points' or 'cells' data 1156 fast : (bool) 1157 if True, will use a less accurate algorithm 1158 that performs fewer derivative calculations (and is therefore faster). 1159 """ 1160 div = vtk.new("GradientFilter") 1161 if on.startswith("p"): 1162 varr = self.dataset.GetPointData() 1163 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1164 elif on.startswith("c"): 1165 varr = self.dataset.GetCellData() 1166 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1167 else: 1168 vedo.logger.error(f"in divergence(): unknown option {on}") 1169 raise RuntimeError 1170 1171 if array_name is None: 1172 if varr.GetVectors(): 1173 array_name = varr.GetVectors().GetName() 1174 else: 1175 vedo.logger.error(f"in divergence(): no vectors found for {on}") 1176 raise RuntimeError 1177 1178 div.SetInputData(self.dataset) 1179 div.SetInputScalars(tp, array_name) 1180 div.ComputeDivergenceOn() 1181 div.ComputeGradientOff() 1182 div.ComputeVorticityOff() 1183 div.SetDivergenceArrayName("Divergence") 1184 div.SetFasterApproximation(fast) 1185 div.Update() 1186 if on.startswith("p"): 1187 dvecs = utils.vtk2numpy(div.GetOutput().GetPointData().GetArray("Divergence")) 1188 else: 1189 dvecs = utils.vtk2numpy(div.GetOutput().GetCellData().GetArray("Divergence")) 1190 return dvecs 1191 1192 def vorticity(self, array_name=None, on="points", fast=False): 1193 """ 1194 Compute and return the vorticity of a vector field as a numpy array. 1195 1196 Arguments: 1197 array_name : (str) 1198 name of the array to compute the vorticity, 1199 if None the current active array is selected 1200 on : (str) 1201 compute either on 'points' or 'cells' data 1202 fast : (bool) 1203 if True, will use a less accurate algorithm 1204 that performs fewer derivative calculations (and is therefore faster). 1205 """ 1206 vort = vtk.new("GradientFilter") 1207 if on.startswith("p"): 1208 varr = self.dataset.GetPointData() 1209 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1210 elif on.startswith("c"): 1211 varr = self.dataset.GetCellData() 1212 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1213 else: 1214 vedo.logger.error(f"in vorticity(): unknown option {on}") 1215 raise RuntimeError 1216 1217 if array_name is None: 1218 if varr.GetVectors(): 1219 array_name = varr.GetVectors().GetName() 1220 else: 1221 vedo.logger.error(f"in vorticity(): no vectors found for {on}") 1222 raise RuntimeError 1223 1224 vort.SetInputData(self.dataset) 1225 vort.SetInputScalars(tp, array_name) 1226 vort.ComputeDivergenceOff() 1227 vort.ComputeGradientOff() 1228 vort.ComputeVorticityOn() 1229 vort.SetVorticityArrayName("Vorticity") 1230 vort.SetFasterApproximation(fast) 1231 vort.Update() 1232 if on.startswith("p"): 1233 vvecs = utils.vtk2numpy(vort.GetOutput().GetPointData().GetArray("Vorticity")) 1234 else: 1235 vvecs = utils.vtk2numpy(vort.GetOutput().GetCellData().GetArray("Vorticity")) 1236 return vvecs 1237 1238 def probe(self, source): 1239 """ 1240 Takes a `Volume` (or any other data set) 1241 and probes its scalars at the specified points in space. 1242 1243 Note that a mask is also output with valid/invalid points which can be accessed 1244 with `mesh.pointdata['ValidPointMask']`. 1245 1246 Check out also: 1247 `interpolate_data_from()` 1248 1249 Examples: 1250 - [probe_points.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_points.py) 1251 1252  1253 """ 1254 probe_filter = vtk.new("ProbeFilter") 1255 probe_filter.SetSourceData(source.dataset) 1256 probe_filter.SetInputData(self.dataset) 1257 probe_filter.Update() 1258 self._update(probe_filter.GetOutput(), reset_locators=False) 1259 self.pipeline = utils.OperationNode("probe", parents=[self, source]) 1260 self.pointdata.rename("vtkValidPointMask", "ValidPointMask") 1261 return self 1262 1263 def compute_cell_size(self): 1264 """ 1265 Add to this object a cell data array 1266 containing the area, volume and edge length 1267 of the cells (when applicable to the object type). 1268 1269 Array names are: `Area`, `Volume`, `Length`. 1270 """ 1271 csf = vtk.new("CellSizeFilter") 1272 csf.SetInputData(self.dataset) 1273 csf.SetComputeArea(1) 1274 csf.SetComputeVolume(1) 1275 csf.SetComputeLength(1) 1276 csf.SetComputeVertexCount(0) 1277 csf.SetAreaArrayName("Area") 1278 csf.SetVolumeArrayName("Volume") 1279 csf.SetLengthArrayName("Length") 1280 csf.Update() 1281 self._update(csf.GetOutput(), reset_locators=False) 1282 return self 1283 1284 def integrate_arrays_over_domain(self): 1285 """ 1286 Integrate point and cell data arrays while computing length, area or volume 1287 of the domain. It works for 1D, 2D or 3D cells. 1288 For volumetric datasets, this filter ignores all but 3D cells. 1289 It will not compute the volume contained in a closed surface. 1290 1291 Returns a dictionary with keys: `pointdata`, `celldata`, `metadata`, 1292 which contain the integration result for the corresponding attributes. 1293 1294 Examples: 1295 ```python 1296 from vedo import * 1297 surf = Sphere(res=100) 1298 surf.pointdata['scalars'] = np.ones(surf.npoints) 1299 data = surf.integrate_arrays_over_domain() 1300 print(data['pointdata']['scalars'], "is equal to 4pi", 4*np.pi) 1301 ``` 1302 1303 ```python 1304 from vedo import * 1305 1306 xcoords1 = np.arange(0, 2.2, 0.2) 1307 xcoords2 = sqrt(np.arange(0, 4.2, 0.2)) 1308 1309 ycoords = np.arange(0, 1.2, 0.2) 1310 1311 surf1 = Grid(s=(xcoords1, ycoords)).rotate_y(-45).lw(2) 1312 surf2 = Grid(s=(xcoords2, ycoords)).rotate_y(-45).lw(2) 1313 1314 surf1.pointdata['scalars'] = surf1.vertices[:,2] 1315 surf2.pointdata['scalars'] = surf2.vertices[:,2] 1316 1317 data1 = surf1.integrate_arrays_over_domain() 1318 data2 = surf2.integrate_arrays_over_domain() 1319 1320 print(data1['pointdata']['scalars'], 1321 "is equal to", 1322 data2['pointdata']['scalars'], 1323 "even if the grids are different!", 1324 "(= the volume under the surface)" 1325 ) 1326 show(surf1, surf2, N=2, axes=1).close() 1327 ``` 1328 """ 1329 vinteg = vtk.new("IntegrateAttributes") 1330 vinteg.SetInputData(self.dataset) 1331 vinteg.Update() 1332 ugrid = vedo.UnstructuredGrid(vinteg.GetOutput()) 1333 data = dict( 1334 pointdata=ugrid.pointdata.todict(), 1335 celldata=ugrid.celldata.todict(), 1336 metadata=ugrid.metadata.todict(), 1337 ) 1338 return data 1339 1340 def write(self, filename, binary=True): 1341 """Write object to file.""" 1342 out = vedo.file_io.write(self, filename, binary) 1343 out.pipeline = utils.OperationNode( 1344 "write", parents=[self], comment=filename[:15], shape="folder", c="#8a817c" 1345 ) 1346 return out 1347 1348 def tomesh(self, bounds=()): 1349 """ 1350 Extract boundary geometry from dataset (or convert data to polygonal type). 1351 1352 Two new arrays are added to the mesh: `OriginalCellIds` and `OriginalPointIds` 1353 to keep track of the original mesh elements. 1354 """ 1355 geo = vtk.new("GeometryFilter") 1356 geo.SetInputData(self.dataset) 1357 geo.SetPassThroughCellIds(1) 1358 geo.SetPassThroughPointIds(1) 1359 geo.SetOriginalCellIdsName("OriginalCellIds") 1360 geo.SetOriginalPointIdsName("OriginalPointIds") 1361 geo.SetNonlinearSubdivisionLevel(1) 1362 geo.MergingOff() 1363 if bounds: 1364 geo.SetExtent(bounds) 1365 geo.ExtentClippingOn() 1366 geo.Update() 1367 msh = vedo.mesh.Mesh(geo.GetOutput()) 1368 msh.pipeline = utils.OperationNode("tomesh", parents=[self], c="#9e2a2b") 1369 return msh 1370 1371 def shrink(self, fraction=0.8): 1372 """ 1373 Shrink the individual cells to improve visibility. 1374 1375  1376 """ 1377 sf = vtk.new("ShrinkFilter") 1378 sf.SetInputData(self.dataset) 1379 sf.SetShrinkFactor(fraction) 1380 sf.Update() 1381 self._update(sf.GetOutput()) 1382 self.pipeline = utils.OperationNode( 1383 "shrink", comment=f"by {fraction}", parents=[self], c="#9e2a2b" 1384 ) 1385 return self
Common algorithms.
Create and/or return a numpy.array
associated to points (vertices).
A data array can be indexed either as a string or by an integer number.
E.g.: myobj.pointdata["arrayname"]
Usage:
myobj.pointdata.keys()
to return the available data array names
myobj.pointdata.select(name)
to make this array the active one
myobj.pointdata.remove(name)
to remove this array
Create and/or return a numpy.array
associated to cells (faces).
A data array can be indexed either as a string or by an integer number.
E.g.: myobj.celldata["arrayname"]
Usage:
myobj.celldata.keys()
to return the available data array names
myobj.celldata.select(name)
to make this array the active one
myobj.celldata.remove(name)
to remove this array
Create and/or return a numpy.array
associated to neither cells nor faces.
A data array can be indexed either as a string or by an integer number.
E.g.: myobj.metadata["arrayname"]
Usage:
myobj.metadata.keys()
to return the available data array names
myobj.metadata.select(name)
to make this array the active one
myobj.metadata.remove(name)
to remove this array
429 def memory_address(self): 430 """ 431 Return a unique memory address integer which may serve as the ID of the 432 object, or passed to c++ code. 433 """ 434 # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/ 435 # https://github.com/tfmoraes/polydata_connectivity 436 return int(self.dataset.GetAddressAsString("")[5:], 16)
Return a unique memory address integer which may serve as the ID of the object, or passed to c++ code.
438 def memory_size(self): 439 """Return the size in bytes of the object in memory.""" 440 return self.dataset.GetActualMemorySize()
Return the size in bytes of the object in memory.
442 def modified(self): 443 """Use in conjunction with `tonumpy()` to update any modifications to the image array.""" 444 self.dataset.GetPointData().Modified() 445 self.dataset.GetPointData().GetScalars().Modified() 446 return self
Use in conjunction with tonumpy()
to update any modifications to the image array.
448 def box(self, scale=1, padding=0): 449 """ 450 Return the bounding box as a new `Mesh` object. 451 452 Arguments: 453 scale : (float) 454 box size can be scaled by a factor 455 padding : (float, list) 456 a constant padding can be added (can be a list `[padx,pady,padz]`) 457 """ 458 b = self.bounds() 459 if not utils.is_sequence(padding): 460 padding = [padding, padding, padding] 461 length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4] 462 tol = (length + width + height) / 30000 # useful for boxing text 463 pos = [(b[0] + b[1])/2, (b[3] + b[2])/2, (b[5] + b[4])/2 - tol] 464 bx = vedo.shapes.Box( 465 pos, 466 length * scale + padding[0], 467 width * scale + padding[1], 468 height * scale + padding[2], 469 c="gray", 470 ) 471 try: 472 pr = vtk.vtkProperty() 473 pr.DeepCopy(self.properties) 474 bx.SetProperty(pr) 475 bx.properties = pr 476 except (AttributeError, TypeError): 477 pass 478 bx.flat().lighting("off").wireframe(True) 479 return bx
Return the bounding box as a new Mesh
object.
Arguments:
- scale : (float) box size can be scaled by a factor
- padding : (float, list)
a constant padding can be added (can be a list
[padx,pady,padz]
)
481 def bounds(self): 482 """ 483 Get the object bounds. 484 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 485 """ 486 try: # this is very slow for large meshes 487 pts = self.vertices 488 xmin, ymin, zmin = np.min(pts, axis=0) 489 xmax, ymax, zmax = np.max(pts, axis=0) 490 return np.array([xmin, xmax, ymin, ymax, zmin, zmax]) 491 except (AttributeError, ValueError): 492 return np.array(self.dataset.GetBounds())
Get the object bounds.
Returns a list in format [xmin,xmax, ymin,ymax, zmin,zmax]
.
494 def xbounds(self, i=None): 495 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 496 b = self.bounds() 497 if i is not None: 498 return b[i] 499 return np.array([b[0], b[1]])
Get the bounds [xmin,xmax]
. Can specify upper or lower with i (0,1).
501 def ybounds(self, i=None): 502 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 503 b = self.bounds() 504 if i == 0: 505 return b[2] 506 if i == 1: 507 return b[3] 508 return np.array([b[2], b[3]])
Get the bounds [ymin,ymax]
. Can specify upper or lower with i (0,1).
510 def zbounds(self, i=None): 511 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 512 b = self.bounds() 513 if i == 0: 514 return b[4] 515 if i == 1: 516 return b[5] 517 return np.array([b[4], b[5]])
Get the bounds [zmin,zmax]
. Can specify upper or lower with i (0,1).
519 def diagonal_size(self): 520 """Get the length of the diagonal of the bounding box.""" 521 b = self.bounds() 522 return np.sqrt((b[1] - b[0])**2 + (b[3] - b[2])**2 + (b[5] - b[4])**2)
Get the length of the diagonal of the bounding box.
524 def average_size(self): 525 """ 526 Calculate and return the average size of the object. 527 This is the mean of the vertex distances from the center of mass. 528 """ 529 coords = self.vertices 530 cm = np.mean(coords, axis=0) 531 if coords.shape[0] == 0: 532 return 0.0 533 cc = coords - cm 534 return np.mean(np.linalg.norm(cc, axis=1))
Calculate and return the average size of the object. This is the mean of the vertex distances from the center of mass.
536 def center_of_mass(self): 537 """Get the center of mass of the object.""" 538 cmf = vtk.new("CenterOfMass") 539 cmf.SetInputData(self.dataset) 540 cmf.Update() 541 c = cmf.GetCenter() 542 return np.array(c)
Get the center of mass of the object.
544 def copy_data_from(self, obj): 545 """Copy all data (point and cell data) from this input object""" 546 self.dataset.GetPointData().PassData(obj.dataset.GetPointData()) 547 self.dataset.GetCellData().PassData(obj.dataset.GetCellData()) 548 self.pipeline = utils.OperationNode( 549 "copy_data_from", 550 parents=[self, obj], 551 comment=f"{obj.__class__.__name__}", 552 shape="note", 553 c="#ccc5b9", 554 ) 555 return self
Copy all data (point and cell data) from this input object
557 def inputdata(self): 558 """Obsolete, use `.dataset` instead.""" 559 colors.printc("WARNING: 'inputdata()' is obsolete, use '.dataset' instead.", c="y") 560 return self.dataset
Obsolete, use .dataset
instead.
577 def points(self, pts=None): 578 """Obsolete, use `self.vertices` or `self.coordinates` instead.""" 579 if pts is None: ### getter 580 581 if warnings["points_getter"]: 582 colors.printc(warnings["points_getter"], c="y") 583 warnings["points_getter"] = "" 584 return self.vertices 585 586 else: ### setter 587 588 if warnings["points_setter"]: 589 colors.printc(warnings["points_setter"], c="y") 590 warnings["points_setter"] = "" 591 592 pts = np.asarray(pts, dtype=np.float32) 593 594 if pts.ndim == 1: 595 ### getter by point index ################### 596 indices = pts.astype(int) 597 vpts = self.dataset.GetPoints() 598 arr = utils.vtk2numpy(vpts.GetData()) 599 return arr[indices] ########### 600 601 ### setter #################################### 602 if pts.shape[1] == 2: 603 pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)] 604 arr = utils.numpy2vtk(pts, dtype=np.float32) 605 606 vpts = self.dataset.GetPoints() 607 vpts.SetData(arr) 608 vpts.Modified() 609 # reset mesh to identity matrix position/rotation: 610 self.point_locator = None 611 self.cell_locator = None 612 self.line_locator = None 613 self.transform = LinearTransform() 614 return self
Obsolete, use self.vertices
or self.coordinates
instead.
Get lines connectivity ids as a python array
formatted as [[id0,id1], [id3,id4], ...]
See also: lines_as_flat_array()
.
Get lines connectivity ids as a 1D numpy array. Format is e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...]
See also: lines()
.
662 def mark_boundaries(self): 663 """ 664 Mark cells and vertices if they lie on a boundary. 665 A new array called `BoundaryCells` is added to the object. 666 """ 667 mb = vtk.new("MarkBoundaryFilter") 668 mb.SetInputData(self.dataset) 669 mb.Update() 670 self.dataset.DeepCopy(mb.GetOutput()) 671 self.pipeline = utils.OperationNode("mark_boundaries", parents=[self]) 672 return self
Mark cells and vertices if they lie on a boundary.
A new array called BoundaryCells
is added to the object.
674 def find_cells_in_bounds(self, xbounds=(), ybounds=(), zbounds=()): 675 """ 676 Find cells that are within the specified bounds. 677 """ 678 try: 679 xbounds = list(xbounds.bounds()) 680 except AttributeError: 681 pass 682 683 if len(xbounds) == 6: 684 bnds = xbounds 685 else: 686 bnds = list(self.bounds()) 687 if len(xbounds) == 2: 688 bnds[0] = xbounds[0] 689 bnds[1] = xbounds[1] 690 if len(ybounds) == 2: 691 bnds[2] = ybounds[0] 692 bnds[3] = ybounds[1] 693 if len(zbounds) == 2: 694 bnds[4] = zbounds[0] 695 bnds[5] = zbounds[1] 696 697 cell_ids = vtk.vtkIdList() 698 if not self.cell_locator: 699 self.cell_locator = vtk.new("CellTreeLocator") 700 self.cell_locator.SetDataSet(self.dataset) 701 self.cell_locator.BuildLocator() 702 self.cell_locator.FindCellsWithinBounds(bnds, cell_ids) 703 cids = [] 704 for i in range(cell_ids.GetNumberOfIds()): 705 cid = cell_ids.GetId(i) 706 cids.append(cid) 707 return np.array(cids)
Find cells that are within the specified bounds.
709 def find_cells_along_line(self, p0, p1, tol=0.001): 710 """ 711 Find cells that are intersected by a line segment. 712 """ 713 cell_ids = vtk.vtkIdList() 714 if not self.cell_locator: 715 self.cell_locator = vtk.new("CellTreeLocator") 716 self.cell_locator.SetDataSet(self.dataset) 717 self.cell_locator.BuildLocator() 718 self.cell_locator.FindCellsAlongLine(p0, p1, tol, cell_ids) 719 cids = [] 720 for i in range(cell_ids.GetNumberOfIds()): 721 cid = cell_ids.GetId(i) 722 cids.append(cid) 723 return np.array(cids)
Find cells that are intersected by a line segment.
725 def find_cells_along_plane(self, origin, normal, tol=0.001): 726 """ 727 Find cells that are intersected by a plane. 728 """ 729 cell_ids = vtk.vtkIdList() 730 if not self.cell_locator: 731 self.cell_locator = vtk.new("CellTreeLocator") 732 self.cell_locator.SetDataSet(self.dataset) 733 self.cell_locator.BuildLocator() 734 self.cell_locator.FindCellsAlongPlane(origin, normal, tol, cell_ids) 735 cids = [] 736 for i in range(cell_ids.GetNumberOfIds()): 737 cid = cell_ids.GetId(i) 738 cids.append(cid) 739 return np.array(cids)
Find cells that are intersected by a plane.
741 def delete_cells_by_point_index(self, indices): 742 """ 743 Delete a list of vertices identified by any of their vertex index. 744 745 See also `delete_cells()`. 746 747 Examples: 748 - [delete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delete_mesh_pts.py) 749 750  751 """ 752 cell_ids = vtk.vtkIdList() 753 self.dataset.BuildLinks() 754 n = 0 755 for i in np.unique(indices): 756 self.dataset.GetPointCells(i, cell_ids) 757 for j in range(cell_ids.GetNumberOfIds()): 758 self.dataset.DeleteCell(cell_ids.GetId(j)) # flag cell 759 n += 1 760 761 self.dataset.RemoveDeletedCells() 762 self.dataset.Modified() 763 self.pipeline = utils.OperationNode( 764 "delete_cells_by_point_index", parents=[self]) 765 return self
Delete a list of vertices identified by any of their vertex index.
See also delete_cells()
.
Examples:
767 def map_cells_to_points(self, arrays=(), move=False): 768 """ 769 Interpolate cell data (i.e., data specified per cell or face) 770 into point data (i.e., data specified at each vertex). 771 The method of transformation is based on averaging the data values 772 of all cells using a particular point. 773 774 A custom list of arrays to be mapped can be passed in input. 775 776 Set `move=True` to delete the original `celldata` array. 777 """ 778 c2p = vtk.new("CellDataToPointData") 779 c2p.SetInputData(self.dataset) 780 if not move: 781 c2p.PassCellDataOn() 782 if arrays: 783 c2p.ClearCellDataArrays() 784 c2p.ProcessAllArraysOff() 785 for arr in arrays: 786 c2p.AddCellDataArray(arr) 787 else: 788 c2p.ProcessAllArraysOn() 789 c2p.Update() 790 self._update(c2p.GetOutput(), reset_locators=False) 791 self.mapper.SetScalarModeToUsePointData() 792 self.pipeline = utils.OperationNode("map_cells_to_points", parents=[self]) 793 return self
Interpolate cell data (i.e., data specified per cell or face) into point data (i.e., data specified at each vertex). The method of transformation is based on averaging the data values of all cells using a particular point.
A custom list of arrays to be mapped can be passed in input.
Set move=True
to delete the original celldata
array.
Get cell connectivity ids as a 1D numpy array. Format is e.g. [3, 10,20,30 4, 10,11,12,13 ...]
Get the cells connectivity ids as a numpy array.
The output format is: [[id0 ... idn], [id0 ... idm], etc]
.
892 def map_points_to_cells(self, arrays=(), move=False): 893 """ 894 Interpolate point data (i.e., data specified per point or vertex) 895 into cell data (i.e., data specified per cell). 896 The method of transformation is based on averaging the data values 897 of all points defining a particular cell. 898 899 A custom list of arrays to be mapped can be passed in input. 900 901 Set `move=True` to delete the original `pointdata` array. 902 903 Examples: 904 - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py) 905 """ 906 p2c = vtk.new("PointDataToCellData") 907 p2c.SetInputData(self.dataset) 908 if not move: 909 p2c.PassPointDataOn() 910 if arrays: 911 p2c.ClearPointDataArrays() 912 p2c.ProcessAllArraysOff() 913 for arr in arrays: 914 p2c.AddPointDataArray(arr) 915 else: 916 p2c.ProcessAllArraysOn() 917 p2c.Update() 918 self._update(p2c.GetOutput(), reset_locators=False) 919 self.mapper.SetScalarModeToUseCellData() 920 self.pipeline = utils.OperationNode("map_points_to_cells", parents=[self]) 921 return self
Interpolate point data (i.e., data specified per point or vertex) into cell data (i.e., data specified per cell). The method of transformation is based on averaging the data values of all points defining a particular cell.
A custom list of arrays to be mapped can be passed in input.
Set move=True
to delete the original pointdata
array.
Examples:
923 def resample_data_from(self, source, tol=None, categorical=False): 924 """ 925 Resample point and cell data from another dataset. 926 The output has the same structure but its point data have 927 the resampled values from target. 928 929 Use `tol` to set the tolerance used to compute whether 930 a point in the source is in a cell of the current object. 931 Points without resampled values, and their cells, are marked as blank. 932 If the data is categorical, then the resulting data will be determined 933 by a nearest neighbor interpolation scheme. 934 935 Example: 936 ```python 937 from vedo import * 938 m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1) 939 pts = m1.vertices 940 ces = m1.cell_centers 941 m1.pointdata["xvalues"] = np.power(pts[:,0], 3) 942 m1.celldata["yvalues"] = np.power(ces[:,1], 3) 943 m2 = Mesh(dataurl+'bunny.obj') 944 m2.resample_arrays_from(m1) 945 # print(m2.pointdata["xvalues"]) 946 show(m1, m2 , N=2, axes=1) 947 ``` 948 """ 949 rs = vtk.new("ResampleWithDataSet") 950 rs.SetInputData(self.dataset) 951 rs.SetSourceData(source.dataset) 952 953 rs.SetPassPointArrays(True) 954 rs.SetPassCellArrays(True) 955 rs.SetPassFieldArrays(True) 956 rs.SetCategoricalData(categorical) 957 958 rs.SetComputeTolerance(True) 959 if tol: 960 rs.SetComputeTolerance(False) 961 rs.SetTolerance(tol) 962 rs.Update() 963 self._update(rs.GetOutput(), reset_locators=False) 964 self.pipeline = utils.OperationNode( 965 "resample_data_from", 966 comment=f"{source.__class__.__name__}", 967 parents=[self, source] 968 ) 969 return self
Resample point and cell data from another dataset. The output has the same structure but its point data have the resampled values from target.
Use tol
to set the tolerance used to compute whether
a point in the source is in a cell of the current object.
Points without resampled values, and their cells, are marked as blank.
If the data is categorical, then the resulting data will be determined
by a nearest neighbor interpolation scheme.
Example:
from vedo import *
m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1)
pts = m1.vertices
ces = m1.cell_centers
m1.pointdata["xvalues"] = np.power(pts[:,0], 3)
m1.celldata["yvalues"] = np.power(ces[:,1], 3)
m2 = Mesh(dataurl+'bunny.obj')
m2.resample_arrays_from(m1)
# print(m2.pointdata["xvalues"])
show(m1, m2 , N=2, axes=1)
971 def interpolate_data_from( 972 self, 973 source, 974 radius=None, 975 n=None, 976 kernel="shepard", 977 exclude=("Normals",), 978 on="points", 979 null_strategy=1, 980 null_value=0, 981 ): 982 """ 983 Interpolate over source to port its data onto the current object using various kernels. 984 985 If n (number of closest points to use) is set then radius value is ignored. 986 987 Check out also: 988 `probe()` which in many cases can be faster. 989 990 Arguments: 991 kernel : (str) 992 available kernels are [shepard, gaussian, linear] 993 null_strategy : (int) 994 specify a strategy to use when encountering a "null" point 995 during the interpolation process. Null points occur when the local neighborhood 996 (of nearby points to interpolate from) is empty. 997 998 - Case 0: an output array is created that marks points 999 as being valid (=1) or null (invalid =0), and the null_value is set as well 1000 - Case 1: the output data value(s) are set to the provided null_value 1001 - Case 2: simply use the closest point to perform the interpolation. 1002 null_value : (float) 1003 see above. 1004 1005 Examples: 1006 - [interpolate_scalar1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar1.py) 1007 - [interpolate_scalar3.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar3.py) 1008 - [interpolate_scalar4.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar4.py) 1009 - [image_probe.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/image_probe.py) 1010 1011  1012 """ 1013 if radius is None and not n: 1014 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 1015 raise RuntimeError 1016 1017 if on == "points": 1018 points = source.dataset 1019 elif on == "cells": 1020 c2p = vtk.new("CellDataToPointData") 1021 c2p.SetInputData(source.dataset) 1022 c2p.Update() 1023 points = c2p.GetOutput() 1024 else: 1025 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 1026 raise RuntimeError() 1027 1028 locator = vtk.new("PointLocator") 1029 locator.SetDataSet(points) 1030 locator.BuildLocator() 1031 1032 if kernel.lower() == "shepard": 1033 kern = vtk.new("ShepardKernel") 1034 kern.SetPowerParameter(2) 1035 elif kernel.lower() == "gaussian": 1036 kern = vtk.new("GaussianKernel") 1037 kern.SetSharpness(2) 1038 elif kernel.lower() == "linear": 1039 kern = vtk.new("LinearKernel") 1040 else: 1041 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 1042 raise RuntimeError() 1043 1044 if n: 1045 kern.SetNumberOfPoints(n) 1046 kern.SetKernelFootprintToNClosest() 1047 else: 1048 kern.SetRadius(radius) 1049 kern.SetKernelFootprintToRadius() 1050 1051 interpolator = vtk.new("PointInterpolator") 1052 interpolator.SetInputData(self.dataset) 1053 interpolator.SetSourceData(points) 1054 interpolator.SetKernel(kern) 1055 interpolator.SetLocator(locator) 1056 interpolator.PassFieldArraysOff() 1057 interpolator.SetNullPointsStrategy(null_strategy) 1058 interpolator.SetNullValue(null_value) 1059 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 1060 for ex in exclude: 1061 interpolator.AddExcludedArray(ex) 1062 interpolator.Update() 1063 1064 if on == "cells": 1065 p2c = vtk.new("PointDataToCellData") 1066 p2c.SetInputData(interpolator.GetOutput()) 1067 p2c.Update() 1068 cpoly = p2c.GetOutput() 1069 else: 1070 cpoly = interpolator.GetOutput() 1071 1072 self._update(cpoly, reset_locators=False) 1073 1074 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 1075 return self
Interpolate over source to port its data onto the current object using various kernels.
If n (number of closest points to use) is set then radius value is ignored.
Check out also:
probe()
which in many cases can be faster.
Arguments:
- kernel : (str) available kernels are [shepard, gaussian, linear]
null_strategy : (int) specify a strategy to use when encountering a "null" point during the interpolation process. Null points occur when the local neighborhood (of nearby points to interpolate from) is empty.
- Case 0: an output array is created that marks points as being valid (=1) or null (invalid =0), and the null_value is set as well
- Case 1: the output data value(s) are set to the provided null_value
- Case 2: simply use the closest point to perform the interpolation.
- null_value : (float) see above.
Examples:
1077 def add_ids(self): 1078 """ 1079 Generate point and cell ids arrays. 1080 1081 Two new arrays are added to the mesh: `PointID` and `CellID`. 1082 """ 1083 ids = vtk.new("IdFilter") 1084 ids.SetInputData(self.dataset) 1085 ids.PointIdsOn() 1086 ids.CellIdsOn() 1087 ids.FieldDataOff() 1088 ids.SetPointIdsArrayName("PointID") 1089 ids.SetCellIdsArrayName("CellID") 1090 ids.Update() 1091 self._update(ids.GetOutput(), reset_locators=False) 1092 self.pipeline = utils.OperationNode("add_ids", parents=[self]) 1093 return self
Generate point and cell ids arrays.
Two new arrays are added to the mesh: PointID
and CellID
.
1095 def gradient(self, input_array=None, on="points", fast=False): 1096 """ 1097 Compute and return the gradiend of the active scalar field as a numpy array. 1098 1099 Arguments: 1100 input_array : (str) 1101 array of the scalars to compute the gradient, 1102 if None the current active array is selected 1103 on : (str) 1104 compute either on 'points' or 'cells' data 1105 fast : (bool) 1106 if True, will use a less accurate algorithm 1107 that performs fewer derivative calculations (and is therefore faster). 1108 1109 Examples: 1110 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py) 1111 1112  1113 """ 1114 gra = vtk.new("GradientFilter") 1115 if on.startswith("p"): 1116 varr = self.dataset.GetPointData() 1117 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1118 elif on.startswith("c"): 1119 varr = self.dataset.GetCellData() 1120 tp = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1121 else: 1122 vedo.logger.error(f"in gradient: unknown option {on}") 1123 raise RuntimeError 1124 1125 if input_array is None: 1126 if varr.GetScalars(): 1127 input_array = varr.GetScalars().GetName() 1128 else: 1129 vedo.