vedo.chemistry
This module provides a Vedo-compatible interface to the periodic table of elements, allowing access to element properties such as atomic numbers, names, symbols, and covalent radiii. It wraps the VTK's vtkPeriodicTable class to provide a more Pythonic interface.
1import numpy as np 2from vtkmodules.util.numpy_support import vtk_to_numpy 3from vtkmodules.vtkCommonDataModel import vtkMolecule 4from vtkmodules.vtkFiltersCore import vtkMoleculeAppend 5from vtkmodules.vtkIOChemistry import vtkPDBReader 6from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper 7from vtkmodules.vtkDomainsChemistry import vtkPeriodicTable, vtkMoleculeMapper 8from vtkmodules.vtkDomainsChemistry import vtkProteinRibbonFilter 9from vtkmodules.vtkCommonCore import vtkIdList 10from vtkmodules.vtkCommonDataModel import vtkPolyData 11 12__doc__ = """ 13This module provides a Vedo-compatible interface to the periodic table of elements, 14allowing access to element properties such as atomic numbers, names, symbols, and covalent radiii. 15It wraps the VTK's vtkPeriodicTable class to provide a more Pythonic interface. 16""" 17 18__docformat__ = "google" 19 20class PeriodicTable: 21 """ 22 A Vedo-compatible class for accessing periodic table data, wrapping vtkPeriodicTable. 23 24 This class provides access to element properties such as atomic numbers, names, 25 symbols, and covalent radii, using VTK's built-in periodic table database. 26 27 Attributes: 28 periodic_table (vtkPeriodicTable): The underlying VTK periodic table object. 29 """ 30 31 def __init__(self): 32 """ 33 Initialize the PeriodicTable with VTK's built-in periodic table data. 34 """ 35 self.periodic_table = vtkPeriodicTable() 36 37 def get_element_name(self, atomic_number): 38 """ 39 Get the name of the element with the given atomic number. 40 41 Arguments: 42 atomic_number : (int) 43 The atomic number of the element. 44 45 Returns: 46 str: The name of the element. 47 """ 48 return self.periodic_table.GetElementName(atomic_number) 49 50 def get_element_symbol(self, atomic_number): 51 """ 52 Get the symbol of the element with the given atomic number. 53 54 Arguments: 55 atomic_number : (int) 56 The atomic number of the element. 57 58 Returns: 59 The symbol of the element. 60 """ 61 return self.periodic_table.GetSymbol(atomic_number) 62 63 def get_atomic_number(self, symbol): 64 """ 65 Get the atomic number of the element with the given symbol. 66 67 Arguments: 68 symbol : (str) 69 The symbol of the element. 70 71 Returns: 72 The atomic number of the element. 73 """ 74 return self.periodic_table.GetAtomicNumber(symbol) 75 76 def get_covalent_radius(self, atomic_number): 77 """ 78 Get the covalent radius of the element with the given atomic number. 79 80 Arguments: 81 atomic_number : (int) 82 The atomic number of the element. 83 84 Returns: 85 The covalent radius of the element. 86 """ 87 return self.periodic_table.GetCovalentRadius(atomic_number) 88 89 def get_vdw_radius(self, atomic_number): 90 """ 91 Get the Van der Waals radius of the element with the given atomic number. 92 93 Arguments: 94 atomic_number: (int) 95 The atomic number of the element. 96 97 Returns: 98 The Van der Waals radius of the element. 99 """ 100 return self.periodic_table.GetVDWRadius(atomic_number) 101 102 def get_number_of_elements(self): 103 """ 104 Get the total number of elements in the periodic table. 105 106 Returns: 107 The number of elements. 108 """ 109 return self.periodic_table.GetNumberOfElements() 110 111 def get_element_data(self, atomic_number): 112 """ 113 Get all data for the element with the given atomic number. 114 115 Arguments: 116 atomic_number : (int) 117 The atomic number of the element. 118 119 Returns: 120 A dictionary containing the element's name, symbol, and radii. 121 """ 122 return { 123 "name": self.get_element_name(atomic_number), 124 "symbol": self.get_element_symbol(atomic_number), 125 "covalent_radius": self.get_covalent_radius(atomic_number), 126 "vdw_radius": self.get_vdw_radius(atomic_number), 127 } 128 129 def __getitem__(self, atomic_number): 130 """ 131 Get element data by atomic number. 132 133 Arguments: 134 atomic_number: (int) 135 The atomic number of the element. 136 137 Returns: 138 A dictionary containing the element's name, symbol, and radii. 139 """ 140 return self.get_element_data(atomic_number) 141 142 def __len__(self): 143 """ 144 Get the number of elements in the periodic table. 145 146 Returns: 147 The number of elements. 148 """ 149 return self.get_number_of_elements() 150 151 def __iter__(self): 152 """ 153 Iterate over all elements in the periodic table. 154 155 Yields: 156 tuple: (atomic_number, element_data) for each element. 157 """ 158 for atomic_number in range(1, self.get_number_of_elements() + 1): 159 yield atomic_number, self.get_element_data(atomic_number) 160 161 def __contains__(self, atomic_number): 162 """ 163 Check if an atomic number is in the periodic table. 164 165 Arguments: 166 atomic_number: (int) 167 The atomic number to check. 168 169 Returns: 170 bool: True if the atomic number exists, False otherwise. 171 """ 172 return 1 <= atomic_number <= self.get_number_of_elements() 173 174 def __str__(self): 175 """Print info about the periodic table.""" 176 from vedo.colors import printc 177 module = self.__class__.__module__ 178 name = self.__class__.__name__ 179 out = printc( 180 f"{module}.{name} at ({hex(id(self))})".ljust(75), 181 bold=True, invert=True, return_string=True, 182 ) 183 out += "\x1b[0m" 184 n = self.get_number_of_elements() 185 out += f"Number of elements : {n}\n" 186 # Example usage of the periodic table 187 atomic_number = 12 188 out += f"Atomic number : {atomic_number} (example entry)\n" 189 out += f"Element name : {self.get_element_name(atomic_number)}\n" 190 out += f"Element symbol : {self.get_element_symbol(atomic_number)}\n" 191 out += f"Covalent radius : {self.get_covalent_radius(atomic_number)}\n" 192 out += f"Van der Waals radius: {self.get_vdw_radius(atomic_number)}\n" 193 return out.rstrip() + "\x1b[0m" 194 195 196 197def append_molecules(molecules): 198 """ 199 Append multiple molecules into a single molecule. 200 201 This function takes a list of Molecule objects and returns a new Molecule 202 object that combines all atoms and bonds from the input molecules. 203 204 Arguments: 205 molecules : (list of Molecule) 206 The molecules to append. 207 208 Returns: 209 Molecule: A new Molecule object containing all atoms and bonds from the input molecules. 210 """ 211 if not molecules: 212 raise ValueError("No molecules provided to append.") 213 214 # Create an instance of vtkMoleculeAppend 215 append_filter = vtkMoleculeAppend() 216 217 # Add each molecule's vtkMolecule to the append filter 218 for mol in molecules: 219 append_filter.AddInputData(mol.molecule) 220 221 # Update the filter to generate the combined molecule 222 append_filter.Update() 223 224 # Get the output molecule from the filter 225 combined_vtk_molecule = append_filter.GetOutput() 226 227 # Create a new Molecule object 228 combined_molecule = Molecule() 229 230 # Set the combined vtkMolecule to the new Molecule object 231 combined_molecule.molecule = combined_vtk_molecule 232 233 # Reconfigure the mapper and actor 234 combined_molecule.mapper.SetInputData(combined_vtk_molecule) 235 combined_molecule.actor.SetMapper(combined_molecule.mapper) 236 237 # Optionally, copy rendering settings from the first molecule 238 if molecules: 239 first_mol = molecules[0] 240 if first_mol.mapper.GetRenderAtoms(): 241 combined_molecule.use_ball_and_stick() 242 else: 243 combined_molecule.use_space_filling() 244 combined_molecule.set_atom_radius_scale( 245 first_mol.mapper.GetAtomicRadiusScaleFactor() 246 ) 247 combined_molecule.set_bond_radius(first_mol.mapper.GetBondRadius()) 248 249 return combined_molecule 250 251 252class Atom: 253 """ 254 A class representing an atom in a molecule, fully wrapping vtkAtom. 255 256 Provides access to all methods and properties of vtkAtom as documented in: 257 https://vtk.org/doc/nightly/html/classvtkAtom.html 258 """ 259 260 def __init__(self, molecule, atom_id): 261 """ 262 Initialize the Atom with a reference to the molecule and its ID. 263 264 Arguments: 265 molecule : (vtkMolecule) 266 The molecule containing this atom. 267 atom_id : (int) 268 The ID of the atom in the molecule. 269 """ 270 self.molecule = molecule 271 self.atom_id = atom_id 272 self.vtk_atom = self.molecule.GetAtom(self.atom_id) 273 274 def get_atomic_number(self): 275 """Get the atomic number of the atom. 276 277 Returns: 278 The atomic number. 279 """ 280 return self.vtk_atom.GetAtomicNumber() 281 282 def set_atomic_number(self, atomic_number): 283 """Set the atomic number of the atom. 284 285 Arguments: 286 atomic_number : (int) 287 The new atomic number. 288 """ 289 self.vtk_atom.SetAtomicNumber(atomic_number) 290 291 def get_position(self): 292 """Get the position of the atom as a NumPy array. 293 294 Returns: 295 Array of shape (3,) with [x, y, z] coordinates. 296 """ 297 pos = self.vtk_atom.GetPosition() 298 return np.array([pos[0], pos[1], pos[2]]) 299 300 def set_position(self, position): 301 """Set the position of the atom. 302 303 Arguments: 304 position : (list or np.ndarray) 305 The new [x, y, z] coordinates. 306 """ 307 pos = np.asarray(position, dtype=float) 308 self.vtk_atom.SetPosition(pos[0], pos[1], pos[2]) 309 310 def get_atom_id(self): 311 """Get the ID of this atom. 312 313 Returns: 314 The atom's ID within the molecule. 315 """ 316 return self.atom_id 317 318 def get_molecule(self): 319 """Get the molecule this atom belongs to. 320 321 Returns: 322 The parent molecule. 323 """ 324 return self.molecule 325 326 def __repr__(self): 327 return f"Atom(ID={self.atom_id}, AtomicNumber={self.atomic_number}, Position={self.position})" 328 329 330class Molecule: 331 def __init__(self, pdb_file=None): 332 # Create an empty molecule 333 self.molecule = vtkMolecule() 334 335 # Configure the mapper and actor for rendering 336 self.mapper = vtkMoleculeMapper() 337 self.actor = vtkActor() 338 self.property = self.actor.GetProperty() 339 340 if pdb_file: 341 # Create and configure the PDB reader 342 reader = vtkPDBReader() 343 reader.SetFileName(pdb_file) 344 reader.Update() 345 346 # Get the PDB data 347 pdb_data = reader.GetOutput() 348 # print the point data available 349 # print(pdb_data.GetPointData()) 350 # Array 0 name = atom_type 351 # Array 1 name = atom_types 352 # Array 2 name = residue 353 # Array 3 name = chain 354 # Array 4 name = secondary_structures 355 # Array 5 name = secondary_structures_begin 356 # Array 6 name = secondary_structures_end 357 # Array 7 name = ishetatm 358 # Array 8 name = model 359 # Array 9 name = rgb_colors 360 # Array 10 name = radius 361 # for i in range(pdb_data.GetPointData().GetNumberOfArrays()): 362 # print(pdb_data.GetPointData().GetArray(i)) 363 points = pdb_data.GetPoints() 364 point_data = pdb_data.GetPointData() 365 366 # Extract atom information and add to molecule 367 for i in range(points.GetNumberOfPoints()): 368 position = points.GetPoint(i) 369 # Default to Carbon if atomic number is not available 370 atomic_number = 6 371 if point_data.GetScalars("atom_type"): 372 atomic_number = int(point_data.GetScalars("atom_type").GetValue(i)) 373 # Add atom to molecule 374 # if point_data.GetScalars("rgb_colors"): 375 # color = point_data.GetScalars("rgb_colors").GetTuple3(i) 376 # self.actor.GetProperty().SetColor(color) 377 self.molecule.AppendAtom(atomic_number, position) 378 379 # Add bonds if available 380 if pdb_data.GetLines(): 381 lines = pdb_data.GetLines() 382 lines.InitTraversal() 383 id_list = vtkIdList() 384 while lines.GetNextCell(id_list): 385 if id_list.GetNumberOfIds() == 2: 386 self.molecule.AppendBond( 387 id_list.GetId(0), 388 id_list.GetId(1), 389 1, # Default to single bond 390 ) 391 392 # Set the molecule as input to the mapper 393 self.mapper.SetInputData(self.molecule) 394 self.actor.SetMapper(self.mapper) 395 396 # Apply default rendering style 397 self.use_ball_and_stick() 398 399 def append_atom(self, position=None, atomic_number=6): 400 """Add an atom to the molecule with optional position and atomic number. 401 402 Arguments: 403 position: (list or np.ndarray) 404 [x, y, z] coordinates. Defaults to [0, 0, 0]. 405 atomic_number : (int) 406 Atomic number (e.g., 6 for Carbon). Defaults to 6. 407 408 Returns: 409 The added atom object. 410 """ 411 if position is None: 412 position = [0, 0, 0] 413 pos = np.asarray(position, dtype=float) 414 vtk_atom = self.molecule.AppendAtom(atomic_number, pos[0], pos[1], pos[2]) 415 atom_id = ( 416 self.molecule.GetNumberOfAtoms() - 1 417 ) # The ID will be the index of the last added atom 418 return Atom(self.molecule, atom_id) 419 420 def get_atom(self, atom_id): 421 """Retrieve an atom by its ID. 422 423 Arguments: 424 atom_id : (int) 425 The ID of the atom. 426 427 Returns: 428 The atom object. 429 """ 430 if atom_id >= self.get_number_of_atoms(): 431 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 432 return Atom(self.molecule, atom_id) 433 434 def remove_atom(self, atom_id): 435 """Remove an atom by its ID. 436 437 Arguments: 438 atom_id (int): The ID of the atom to remove. 439 """ 440 if atom_id >= self.get_number_of_atoms(): 441 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 442 self.molecule.RemoveAtom(atom_id) 443 # Update the mapper to reflect the changes 444 self.mapper.SetInputData(self.molecule) 445 self.mapper.Update() 446 # Update the actor to reflect the changes 447 self.actor.SetMapper(self.mapper) 448 self.actor.Update() 449 # Update the property to reflect the changes 450 self.property.SetColor(1.0, 0.8, 0.6) # Reset to default color 451 self.property.SetOpacity(1.0) 452 453 def get_array(self, name): 454 """Get a point data array by name. 455 456 The following arrays are available: 457 458 - atom_type: Atomic number of the atom. 459 - atom_types: Atomic type of the atom. 460 - residue: Residue name. 461 - chain: Chain identifier. 462 - secondary_structures: Secondary structure type. 463 - secondary_structures_begin: Start index of the secondary structure. 464 - secondary_structures_end: End index of the secondary structure. 465 - ishetatm: Is the atom a heteroatom? 466 - model: Model number. 467 - rgb_colors: RGB color of the atom. 468 - radius: Radius of the atom. 469 470 Arguments: 471 name : (str) 472 The name of the array. 473 474 Returns: 475 The array data. 476 """ 477 if not self.molecule.GetPointData().HasArray(name): 478 raise ValueError(f"Array '{name}' not found in molecule.") 479 return vtk_to_numpy(self.molecule.GetPointData().GetArray(name)) 480 481 def append_bond(self, atom1, atom2, order=1): 482 """Add a bond between two atoms. 483 484 Arguments: 485 atom1 : (Atom or int) 486 The first atom or its ID. 487 atom2 : (Atom or int) 488 The second atom or its ID. 489 order : (int) 490 Bond order (1=single, 2=double, 3=triple). 491 """ 492 atom1_id = atom1.atom_id if isinstance(atom1, Atom) else atom1 493 atom2_id = atom2.atom_id if isinstance(atom2, Atom) else atom2 494 self.molecule.AppendBond(atom1_id, atom2_id, order) 495 496 def get_number_of_atoms(self): 497 """Get the number of atoms in the molecule. 498 499 Returns: 500 Number of atoms. 501 """ 502 return self.molecule.GetNumberOfAtoms() 503 504 def get_number_of_bonds(self): 505 """Get the number of bonds in the molecule. 506 507 Returns: 508 Number of bonds. 509 """ 510 return self.molecule.GetNumberOfBonds() 511 512 def get_bond(self, bond_id): 513 """Get bond information by ID (simplified as VTK bond access is limited). 514 515 Arguments: 516 bond_id : (int) 517 The ID of the bond. 518 519 Returns: 520 (atom1_id, atom2_id, order). 521 """ 522 if bond_id >= self.get_number_of_bonds(): 523 raise ValueError(f"Bond ID {bond_id} exceeds number of bonds.") 524 bond = self.molecule.GetBond(bond_id) 525 return (bond.GetBeginAtomId(), bond.GetEndAtomId(), bond.GetOrder()) 526 527 def get_atom_positions(self): 528 """Get the positions of all atoms. 529 530 Returns: 531 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 532 """ 533 n_atoms = self.get_number_of_atoms() 534 positions = np.zeros((n_atoms, 3)) 535 for i in range(n_atoms): 536 pos = self.molecule.GetAtom(i).GetPosition() 537 positions[i] = [pos[0], pos[1], pos[2]] 538 return positions 539 540 def set_atom_positions(self, positions): 541 """ 542 Set the positions of all atoms. 543 544 Arguments: 545 positions : (np.ndarray) 546 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 547 """ 548 n_atoms = self.get_number_of_atoms() 549 positions = np.asarray(positions, dtype=float) 550 if positions.shape != (n_atoms, 3): 551 raise ValueError( 552 f"Expected positions shape ({n_atoms}, 3), got {positions.shape}" 553 ) 554 for i in range(n_atoms): 555 self.molecule.GetAtom(i).SetPosition(positions[i]) 556 557 def get_atomic_numbers(self): 558 """ 559 Get the atomic numbers of all atoms. 560 561 Returns: 562 List of atomic numbers. 563 """ 564 return [ 565 self.molecule.GetAtom(i).GetAtomicNumber() 566 for i in range(self.get_number_of_atoms()) 567 ] 568 569 def set_atomic_numbers(self, atomic_numbers): 570 """ 571 Set the atomic numbers of all atoms. 572 573 Arguments: 574 atomic_numbers : (list) 575 List of atomic numbers. 576 """ 577 n_atoms = self.get_number_of_atoms() 578 if len(atomic_numbers) != n_atoms: 579 raise ValueError( 580 f"Expected {n_atoms} atomic numbers, got {len(atomic_numbers)}" 581 ) 582 for i, num in enumerate(atomic_numbers): 583 self.molecule.GetAtom(i).SetAtomicNumber(num) 584 585 # Rendering customization methods 586 def use_ball_and_stick(self): 587 """Set the molecule to use ball-and-stick representation.""" 588 self.mapper.UseBallAndStickSettings() 589 return self 590 591 def use_space_filling(self): 592 """Set the molecule to use space-filling (VDW spheres) representation.""" 593 self.mapper.UseVDWSpheresSettings() 594 return self 595 596 def set_atom_radius_scale(self, scale): 597 """Set the scale factor for atom radii. 598 599 Arguments: 600 scale : (float) 601 Scaling factor for atom spheres. 602 """ 603 self.mapper.SetAtomicRadiusScaleFactor(scale) 604 return self 605 606 def set_bond_radius(self, radius): 607 """Set the radius of bonds. 608 609 Arguments: 610 radius : (float) 611 Bond radius in world units. 612 """ 613 self.mapper.SetBondRadius(radius) 614 return self 615 616 617class Protein: 618 """ 619 A Vedo-compatible class for protein ribbon visualization, wrapping vtkProteinRibbonFilter. 620 621 This class generates a ribbon representation of protein structures from PDB files, 622 vtkMolecule objects, or vtkPolyData, and integrates with Vedo's rendering system. 623 624 Attributes: 625 filter (vtkProteinRibbonFilter): The underlying VTK filter for ribbon generation. 626 mapper (vtkPolyDataMapper): Maps the filter's output to renderable data. 627 actor (vtkActor): The VTK actor for rendering the ribbon. 628 """ 629 630 def __init__(self, input_data): 631 """ 632 Initialize the ProteinRibbon with input data. 633 634 Arguments: 635 input_data : (str or vtkMolecule or vtkPolyData) 636 - Path to a PDB file (str) 637 - A vtkMolecule object 638 - A vtkPolyData object 639 640 Raises: 641 `ValueError` If the input_data type is not supported. 642 """ 643 644 # Handle different input types 645 if isinstance(input_data, str): 646 # Read PDB file using vtkPDBReader 647 reader = vtkPDBReader() 648 reader.SetFileName(input_data) 649 reader.Update() 650 self.input_data = reader.GetOutput() 651 elif isinstance(input_data, vtkMolecule): 652 self.input_data = input_data 653 elif isinstance(input_data, vtkPolyData): 654 self.input_data = input_data 655 else: 656 raise ValueError( 657 "Input must be a PDB file path, vtkMolecule, or vtkPolyData." 658 ) 659 660 # Create and configure the ribbon filter 661 self.filter = vtkProteinRibbonFilter() 662 self.filter.SetInputData(self.input_data) 663 self.filter.Update() 664 665 # Set up the mapper and actor for rendering 666 self.mapper = vtkPolyDataMapper() 667 self.mapper.SetInputConnection(self.filter.GetOutputPort()) 668 self.actor = vtkActor() 669 self.actor.SetMapper(self.mapper) 670 self.property = self.actor.GetProperty() 671 672 # Set default visual properties 673 self.property.SetColor(1.0, 0.8, 0.6) # Soft peach color 674 self.property.SetOpacity(1.0) 675 676 def set_coil_width(self, width): 677 """ 678 Set the width of the coil regions in the ribbon. 679 680 Arguments: 681 width : (float) 682 The width of the coil regions. 683 """ 684 self.filter.SetCoilWidth(width) 685 self.filter.Update() 686 return self 687 688 def set_helix_width(self, width): 689 """ 690 Set the width of the helix regions in the ribbon. 691 692 Arguments: 693 width : (float) 694 The width of the helix regions. 695 """ 696 self.filter.SetHelixWidth(width) 697 self.filter.Update() 698 return self 699 700 def set_sphere_resolution(self, resolution): 701 """ 702 Set the resolution of spheres used in the ribbon representation. 703 704 Arguments: 705 resolution : (int) 706 The resolution of the spheres. 707 """ 708 self.filter.SetSphereResolution(resolution) 709 self.filter.Update() 710 return self
21class PeriodicTable: 22 """ 23 A Vedo-compatible class for accessing periodic table data, wrapping vtkPeriodicTable. 24 25 This class provides access to element properties such as atomic numbers, names, 26 symbols, and covalent radii, using VTK's built-in periodic table database. 27 28 Attributes: 29 periodic_table (vtkPeriodicTable): The underlying VTK periodic table object. 30 """ 31 32 def __init__(self): 33 """ 34 Initialize the PeriodicTable with VTK's built-in periodic table data. 35 """ 36 self.periodic_table = vtkPeriodicTable() 37 38 def get_element_name(self, atomic_number): 39 """ 40 Get the name of the element with the given atomic number. 41 42 Arguments: 43 atomic_number : (int) 44 The atomic number of the element. 45 46 Returns: 47 str: The name of the element. 48 """ 49 return self.periodic_table.GetElementName(atomic_number) 50 51 def get_element_symbol(self, atomic_number): 52 """ 53 Get the symbol of the element with the given atomic number. 54 55 Arguments: 56 atomic_number : (int) 57 The atomic number of the element. 58 59 Returns: 60 The symbol of the element. 61 """ 62 return self.periodic_table.GetSymbol(atomic_number) 63 64 def get_atomic_number(self, symbol): 65 """ 66 Get the atomic number of the element with the given symbol. 67 68 Arguments: 69 symbol : (str) 70 The symbol of the element. 71 72 Returns: 73 The atomic number of the element. 74 """ 75 return self.periodic_table.GetAtomicNumber(symbol) 76 77 def get_covalent_radius(self, atomic_number): 78 """ 79 Get the covalent radius of the element with the given atomic number. 80 81 Arguments: 82 atomic_number : (int) 83 The atomic number of the element. 84 85 Returns: 86 The covalent radius of the element. 87 """ 88 return self.periodic_table.GetCovalentRadius(atomic_number) 89 90 def get_vdw_radius(self, atomic_number): 91 """ 92 Get the Van der Waals radius of the element with the given atomic number. 93 94 Arguments: 95 atomic_number: (int) 96 The atomic number of the element. 97 98 Returns: 99 The Van der Waals radius of the element. 100 """ 101 return self.periodic_table.GetVDWRadius(atomic_number) 102 103 def get_number_of_elements(self): 104 """ 105 Get the total number of elements in the periodic table. 106 107 Returns: 108 The number of elements. 109 """ 110 return self.periodic_table.GetNumberOfElements() 111 112 def get_element_data(self, atomic_number): 113 """ 114 Get all data for the element with the given atomic number. 115 116 Arguments: 117 atomic_number : (int) 118 The atomic number of the element. 119 120 Returns: 121 A dictionary containing the element's name, symbol, and radii. 122 """ 123 return { 124 "name": self.get_element_name(atomic_number), 125 "symbol": self.get_element_symbol(atomic_number), 126 "covalent_radius": self.get_covalent_radius(atomic_number), 127 "vdw_radius": self.get_vdw_radius(atomic_number), 128 } 129 130 def __getitem__(self, atomic_number): 131 """ 132 Get element data by atomic number. 133 134 Arguments: 135 atomic_number: (int) 136 The atomic number of the element. 137 138 Returns: 139 A dictionary containing the element's name, symbol, and radii. 140 """ 141 return self.get_element_data(atomic_number) 142 143 def __len__(self): 144 """ 145 Get the number of elements in the periodic table. 146 147 Returns: 148 The number of elements. 149 """ 150 return self.get_number_of_elements() 151 152 def __iter__(self): 153 """ 154 Iterate over all elements in the periodic table. 155 156 Yields: 157 tuple: (atomic_number, element_data) for each element. 158 """ 159 for atomic_number in range(1, self.get_number_of_elements() + 1): 160 yield atomic_number, self.get_element_data(atomic_number) 161 162 def __contains__(self, atomic_number): 163 """ 164 Check if an atomic number is in the periodic table. 165 166 Arguments: 167 atomic_number: (int) 168 The atomic number to check. 169 170 Returns: 171 bool: True if the atomic number exists, False otherwise. 172 """ 173 return 1 <= atomic_number <= self.get_number_of_elements() 174 175 def __str__(self): 176 """Print info about the periodic table.""" 177 from vedo.colors import printc 178 module = self.__class__.__module__ 179 name = self.__class__.__name__ 180 out = printc( 181 f"{module}.{name} at ({hex(id(self))})".ljust(75), 182 bold=True, invert=True, return_string=True, 183 ) 184 out += "\x1b[0m" 185 n = self.get_number_of_elements() 186 out += f"Number of elements : {n}\n" 187 # Example usage of the periodic table 188 atomic_number = 12 189 out += f"Atomic number : {atomic_number} (example entry)\n" 190 out += f"Element name : {self.get_element_name(atomic_number)}\n" 191 out += f"Element symbol : {self.get_element_symbol(atomic_number)}\n" 192 out += f"Covalent radius : {self.get_covalent_radius(atomic_number)}\n" 193 out += f"Van der Waals radius: {self.get_vdw_radius(atomic_number)}\n" 194 return out.rstrip() + "\x1b[0m"
A Vedo-compatible class for accessing periodic table data, wrapping vtkPeriodicTable.
This class provides access to element properties such as atomic numbers, names, symbols, and covalent radii, using VTK's built-in periodic table database.
Attributes:
- periodic_table (vtkPeriodicTable): The underlying VTK periodic table object.
32 def __init__(self): 33 """ 34 Initialize the PeriodicTable with VTK's built-in periodic table data. 35 """ 36 self.periodic_table = vtkPeriodicTable()
Initialize the PeriodicTable with VTK's built-in periodic table data.
38 def get_element_name(self, atomic_number): 39 """ 40 Get the name of the element with the given atomic number. 41 42 Arguments: 43 atomic_number : (int) 44 The atomic number of the element. 45 46 Returns: 47 str: The name of the element. 48 """ 49 return self.periodic_table.GetElementName(atomic_number)
Get the name of the element with the given atomic number.
Arguments:
- atomic_number : (int) The atomic number of the element.
Returns:
str: The name of the element.
51 def get_element_symbol(self, atomic_number): 52 """ 53 Get the symbol of the element with the given atomic number. 54 55 Arguments: 56 atomic_number : (int) 57 The atomic number of the element. 58 59 Returns: 60 The symbol of the element. 61 """ 62 return self.periodic_table.GetSymbol(atomic_number)
Get the symbol of the element with the given atomic number.
Arguments:
- atomic_number : (int) The atomic number of the element.
Returns:
The symbol of the element.
64 def get_atomic_number(self, symbol): 65 """ 66 Get the atomic number of the element with the given symbol. 67 68 Arguments: 69 symbol : (str) 70 The symbol of the element. 71 72 Returns: 73 The atomic number of the element. 74 """ 75 return self.periodic_table.GetAtomicNumber(symbol)
Get the atomic number of the element with the given symbol.
Arguments:
- symbol : (str) The symbol of the element.
Returns:
The atomic number of the element.
77 def get_covalent_radius(self, atomic_number): 78 """ 79 Get the covalent radius of the element with the given atomic number. 80 81 Arguments: 82 atomic_number : (int) 83 The atomic number of the element. 84 85 Returns: 86 The covalent radius of the element. 87 """ 88 return self.periodic_table.GetCovalentRadius(atomic_number)
Get the covalent radius of the element with the given atomic number.
Arguments:
- atomic_number : (int) The atomic number of the element.
Returns:
The covalent radius of the element.
90 def get_vdw_radius(self, atomic_number): 91 """ 92 Get the Van der Waals radius of the element with the given atomic number. 93 94 Arguments: 95 atomic_number: (int) 96 The atomic number of the element. 97 98 Returns: 99 The Van der Waals radius of the element. 100 """ 101 return self.periodic_table.GetVDWRadius(atomic_number)
Get the Van der Waals radius of the element with the given atomic number.
Arguments:
- atomic_number: (int) The atomic number of the element.
Returns:
The Van der Waals radius of the element.
103 def get_number_of_elements(self): 104 """ 105 Get the total number of elements in the periodic table. 106 107 Returns: 108 The number of elements. 109 """ 110 return self.periodic_table.GetNumberOfElements()
Get the total number of elements in the periodic table.
Returns:
The number of elements.
112 def get_element_data(self, atomic_number): 113 """ 114 Get all data for the element with the given atomic number. 115 116 Arguments: 117 atomic_number : (int) 118 The atomic number of the element. 119 120 Returns: 121 A dictionary containing the element's name, symbol, and radii. 122 """ 123 return { 124 "name": self.get_element_name(atomic_number), 125 "symbol": self.get_element_symbol(atomic_number), 126 "covalent_radius": self.get_covalent_radius(atomic_number), 127 "vdw_radius": self.get_vdw_radius(atomic_number), 128 }
Get all data for the element with the given atomic number.
Arguments:
- atomic_number : (int) The atomic number of the element.
Returns:
A dictionary containing the element's name, symbol, and radii.
198def append_molecules(molecules): 199 """ 200 Append multiple molecules into a single molecule. 201 202 This function takes a list of Molecule objects and returns a new Molecule 203 object that combines all atoms and bonds from the input molecules. 204 205 Arguments: 206 molecules : (list of Molecule) 207 The molecules to append. 208 209 Returns: 210 Molecule: A new Molecule object containing all atoms and bonds from the input molecules. 211 """ 212 if not molecules: 213 raise ValueError("No molecules provided to append.") 214 215 # Create an instance of vtkMoleculeAppend 216 append_filter = vtkMoleculeAppend() 217 218 # Add each molecule's vtkMolecule to the append filter 219 for mol in molecules: 220 append_filter.AddInputData(mol.molecule) 221 222 # Update the filter to generate the combined molecule 223 append_filter.Update() 224 225 # Get the output molecule from the filter 226 combined_vtk_molecule = append_filter.GetOutput() 227 228 # Create a new Molecule object 229 combined_molecule = Molecule() 230 231 # Set the combined vtkMolecule to the new Molecule object 232 combined_molecule.molecule = combined_vtk_molecule 233 234 # Reconfigure the mapper and actor 235 combined_molecule.mapper.SetInputData(combined_vtk_molecule) 236 combined_molecule.actor.SetMapper(combined_molecule.mapper) 237 238 # Optionally, copy rendering settings from the first molecule 239 if molecules: 240 first_mol = molecules[0] 241 if first_mol.mapper.GetRenderAtoms(): 242 combined_molecule.use_ball_and_stick() 243 else: 244 combined_molecule.use_space_filling() 245 combined_molecule.set_atom_radius_scale( 246 first_mol.mapper.GetAtomicRadiusScaleFactor() 247 ) 248 combined_molecule.set_bond_radius(first_mol.mapper.GetBondRadius()) 249 250 return combined_molecule
Append multiple molecules into a single molecule.
This function takes a list of Molecule objects and returns a new Molecule object that combines all atoms and bonds from the input molecules.
Arguments:
- molecules : (list of Molecule) The molecules to append.
Returns:
Molecule: A new Molecule object containing all atoms and bonds from the input molecules.
253class Atom: 254 """ 255 A class representing an atom in a molecule, fully wrapping vtkAtom. 256 257 Provides access to all methods and properties of vtkAtom as documented in: 258 https://vtk.org/doc/nightly/html/classvtkAtom.html 259 """ 260 261 def __init__(self, molecule, atom_id): 262 """ 263 Initialize the Atom with a reference to the molecule and its ID. 264 265 Arguments: 266 molecule : (vtkMolecule) 267 The molecule containing this atom. 268 atom_id : (int) 269 The ID of the atom in the molecule. 270 """ 271 self.molecule = molecule 272 self.atom_id = atom_id 273 self.vtk_atom = self.molecule.GetAtom(self.atom_id) 274 275 def get_atomic_number(self): 276 """Get the atomic number of the atom. 277 278 Returns: 279 The atomic number. 280 """ 281 return self.vtk_atom.GetAtomicNumber() 282 283 def set_atomic_number(self, atomic_number): 284 """Set the atomic number of the atom. 285 286 Arguments: 287 atomic_number : (int) 288 The new atomic number. 289 """ 290 self.vtk_atom.SetAtomicNumber(atomic_number) 291 292 def get_position(self): 293 """Get the position of the atom as a NumPy array. 294 295 Returns: 296 Array of shape (3,) with [x, y, z] coordinates. 297 """ 298 pos = self.vtk_atom.GetPosition() 299 return np.array([pos[0], pos[1], pos[2]]) 300 301 def set_position(self, position): 302 """Set the position of the atom. 303 304 Arguments: 305 position : (list or np.ndarray) 306 The new [x, y, z] coordinates. 307 """ 308 pos = np.asarray(position, dtype=float) 309 self.vtk_atom.SetPosition(pos[0], pos[1], pos[2]) 310 311 def get_atom_id(self): 312 """Get the ID of this atom. 313 314 Returns: 315 The atom's ID within the molecule. 316 """ 317 return self.atom_id 318 319 def get_molecule(self): 320 """Get the molecule this atom belongs to. 321 322 Returns: 323 The parent molecule. 324 """ 325 return self.molecule 326 327 def __repr__(self): 328 return f"Atom(ID={self.atom_id}, AtomicNumber={self.atomic_number}, Position={self.position})"
A class representing an atom in a molecule, fully wrapping vtkAtom.
Provides access to all methods and properties of vtkAtom as documented in: https://vtk.org/doc/nightly/html/classvtkAtom.html
261 def __init__(self, molecule, atom_id): 262 """ 263 Initialize the Atom with a reference to the molecule and its ID. 264 265 Arguments: 266 molecule : (vtkMolecule) 267 The molecule containing this atom. 268 atom_id : (int) 269 The ID of the atom in the molecule. 270 """ 271 self.molecule = molecule 272 self.atom_id = atom_id 273 self.vtk_atom = self.molecule.GetAtom(self.atom_id)
Initialize the Atom with a reference to the molecule and its ID.
Arguments:
- molecule : (vtkMolecule) The molecule containing this atom.
- atom_id : (int) The ID of the atom in the molecule.
275 def get_atomic_number(self): 276 """Get the atomic number of the atom. 277 278 Returns: 279 The atomic number. 280 """ 281 return self.vtk_atom.GetAtomicNumber()
Get the atomic number of the atom.
Returns:
The atomic number.
283 def set_atomic_number(self, atomic_number): 284 """Set the atomic number of the atom. 285 286 Arguments: 287 atomic_number : (int) 288 The new atomic number. 289 """ 290 self.vtk_atom.SetAtomicNumber(atomic_number)
Set the atomic number of the atom.
Arguments:
- atomic_number : (int) The new atomic number.
292 def get_position(self): 293 """Get the position of the atom as a NumPy array. 294 295 Returns: 296 Array of shape (3,) with [x, y, z] coordinates. 297 """ 298 pos = self.vtk_atom.GetPosition() 299 return np.array([pos[0], pos[1], pos[2]])
Get the position of the atom as a NumPy array.
Returns:
Array of shape (3,) with [x, y, z] coordinates.
301 def set_position(self, position): 302 """Set the position of the atom. 303 304 Arguments: 305 position : (list or np.ndarray) 306 The new [x, y, z] coordinates. 307 """ 308 pos = np.asarray(position, dtype=float) 309 self.vtk_atom.SetPosition(pos[0], pos[1], pos[2])
Set the position of the atom.
Arguments:
- position : (list or np.ndarray) The new [x, y, z] coordinates.
331class Molecule: 332 def __init__(self, pdb_file=None): 333 # Create an empty molecule 334 self.molecule = vtkMolecule() 335 336 # Configure the mapper and actor for rendering 337 self.mapper = vtkMoleculeMapper() 338 self.actor = vtkActor() 339 self.property = self.actor.GetProperty() 340 341 if pdb_file: 342 # Create and configure the PDB reader 343 reader = vtkPDBReader() 344 reader.SetFileName(pdb_file) 345 reader.Update() 346 347 # Get the PDB data 348 pdb_data = reader.GetOutput() 349 # print the point data available 350 # print(pdb_data.GetPointData()) 351 # Array 0 name = atom_type 352 # Array 1 name = atom_types 353 # Array 2 name = residue 354 # Array 3 name = chain 355 # Array 4 name = secondary_structures 356 # Array 5 name = secondary_structures_begin 357 # Array 6 name = secondary_structures_end 358 # Array 7 name = ishetatm 359 # Array 8 name = model 360 # Array 9 name = rgb_colors 361 # Array 10 name = radius 362 # for i in range(pdb_data.GetPointData().GetNumberOfArrays()): 363 # print(pdb_data.GetPointData().GetArray(i)) 364 points = pdb_data.GetPoints() 365 point_data = pdb_data.GetPointData() 366 367 # Extract atom information and add to molecule 368 for i in range(points.GetNumberOfPoints()): 369 position = points.GetPoint(i) 370 # Default to Carbon if atomic number is not available 371 atomic_number = 6 372 if point_data.GetScalars("atom_type"): 373 atomic_number = int(point_data.GetScalars("atom_type").GetValue(i)) 374 # Add atom to molecule 375 # if point_data.GetScalars("rgb_colors"): 376 # color = point_data.GetScalars("rgb_colors").GetTuple3(i) 377 # self.actor.GetProperty().SetColor(color) 378 self.molecule.AppendAtom(atomic_number, position) 379 380 # Add bonds if available 381 if pdb_data.GetLines(): 382 lines = pdb_data.GetLines() 383 lines.InitTraversal() 384 id_list = vtkIdList() 385 while lines.GetNextCell(id_list): 386 if id_list.GetNumberOfIds() == 2: 387 self.molecule.AppendBond( 388 id_list.GetId(0), 389 id_list.GetId(1), 390 1, # Default to single bond 391 ) 392 393 # Set the molecule as input to the mapper 394 self.mapper.SetInputData(self.molecule) 395 self.actor.SetMapper(self.mapper) 396 397 # Apply default rendering style 398 self.use_ball_and_stick() 399 400 def append_atom(self, position=None, atomic_number=6): 401 """Add an atom to the molecule with optional position and atomic number. 402 403 Arguments: 404 position: (list or np.ndarray) 405 [x, y, z] coordinates. Defaults to [0, 0, 0]. 406 atomic_number : (int) 407 Atomic number (e.g., 6 for Carbon). Defaults to 6. 408 409 Returns: 410 The added atom object. 411 """ 412 if position is None: 413 position = [0, 0, 0] 414 pos = np.asarray(position, dtype=float) 415 vtk_atom = self.molecule.AppendAtom(atomic_number, pos[0], pos[1], pos[2]) 416 atom_id = ( 417 self.molecule.GetNumberOfAtoms() - 1 418 ) # The ID will be the index of the last added atom 419 return Atom(self.molecule, atom_id) 420 421 def get_atom(self, atom_id): 422 """Retrieve an atom by its ID. 423 424 Arguments: 425 atom_id : (int) 426 The ID of the atom. 427 428 Returns: 429 The atom object. 430 """ 431 if atom_id >= self.get_number_of_atoms(): 432 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 433 return Atom(self.molecule, atom_id) 434 435 def remove_atom(self, atom_id): 436 """Remove an atom by its ID. 437 438 Arguments: 439 atom_id (int): The ID of the atom to remove. 440 """ 441 if atom_id >= self.get_number_of_atoms(): 442 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 443 self.molecule.RemoveAtom(atom_id) 444 # Update the mapper to reflect the changes 445 self.mapper.SetInputData(self.molecule) 446 self.mapper.Update() 447 # Update the actor to reflect the changes 448 self.actor.SetMapper(self.mapper) 449 self.actor.Update() 450 # Update the property to reflect the changes 451 self.property.SetColor(1.0, 0.8, 0.6) # Reset to default color 452 self.property.SetOpacity(1.0) 453 454 def get_array(self, name): 455 """Get a point data array by name. 456 457 The following arrays are available: 458 459 - atom_type: Atomic number of the atom. 460 - atom_types: Atomic type of the atom. 461 - residue: Residue name. 462 - chain: Chain identifier. 463 - secondary_structures: Secondary structure type. 464 - secondary_structures_begin: Start index of the secondary structure. 465 - secondary_structures_end: End index of the secondary structure. 466 - ishetatm: Is the atom a heteroatom? 467 - model: Model number. 468 - rgb_colors: RGB color of the atom. 469 - radius: Radius of the atom. 470 471 Arguments: 472 name : (str) 473 The name of the array. 474 475 Returns: 476 The array data. 477 """ 478 if not self.molecule.GetPointData().HasArray(name): 479 raise ValueError(f"Array '{name}' not found in molecule.") 480 return vtk_to_numpy(self.molecule.GetPointData().GetArray(name)) 481 482 def append_bond(self, atom1, atom2, order=1): 483 """Add a bond between two atoms. 484 485 Arguments: 486 atom1 : (Atom or int) 487 The first atom or its ID. 488 atom2 : (Atom or int) 489 The second atom or its ID. 490 order : (int) 491 Bond order (1=single, 2=double, 3=triple). 492 """ 493 atom1_id = atom1.atom_id if isinstance(atom1, Atom) else atom1 494 atom2_id = atom2.atom_id if isinstance(atom2, Atom) else atom2 495 self.molecule.AppendBond(atom1_id, atom2_id, order) 496 497 def get_number_of_atoms(self): 498 """Get the number of atoms in the molecule. 499 500 Returns: 501 Number of atoms. 502 """ 503 return self.molecule.GetNumberOfAtoms() 504 505 def get_number_of_bonds(self): 506 """Get the number of bonds in the molecule. 507 508 Returns: 509 Number of bonds. 510 """ 511 return self.molecule.GetNumberOfBonds() 512 513 def get_bond(self, bond_id): 514 """Get bond information by ID (simplified as VTK bond access is limited). 515 516 Arguments: 517 bond_id : (int) 518 The ID of the bond. 519 520 Returns: 521 (atom1_id, atom2_id, order). 522 """ 523 if bond_id >= self.get_number_of_bonds(): 524 raise ValueError(f"Bond ID {bond_id} exceeds number of bonds.") 525 bond = self.molecule.GetBond(bond_id) 526 return (bond.GetBeginAtomId(), bond.GetEndAtomId(), bond.GetOrder()) 527 528 def get_atom_positions(self): 529 """Get the positions of all atoms. 530 531 Returns: 532 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 533 """ 534 n_atoms = self.get_number_of_atoms() 535 positions = np.zeros((n_atoms, 3)) 536 for i in range(n_atoms): 537 pos = self.molecule.GetAtom(i).GetPosition() 538 positions[i] = [pos[0], pos[1], pos[2]] 539 return positions 540 541 def set_atom_positions(self, positions): 542 """ 543 Set the positions of all atoms. 544 545 Arguments: 546 positions : (np.ndarray) 547 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 548 """ 549 n_atoms = self.get_number_of_atoms() 550 positions = np.asarray(positions, dtype=float) 551 if positions.shape != (n_atoms, 3): 552 raise ValueError( 553 f"Expected positions shape ({n_atoms}, 3), got {positions.shape}" 554 ) 555 for i in range(n_atoms): 556 self.molecule.GetAtom(i).SetPosition(positions[i]) 557 558 def get_atomic_numbers(self): 559 """ 560 Get the atomic numbers of all atoms. 561 562 Returns: 563 List of atomic numbers. 564 """ 565 return [ 566 self.molecule.GetAtom(i).GetAtomicNumber() 567 for i in range(self.get_number_of_atoms()) 568 ] 569 570 def set_atomic_numbers(self, atomic_numbers): 571 """ 572 Set the atomic numbers of all atoms. 573 574 Arguments: 575 atomic_numbers : (list) 576 List of atomic numbers. 577 """ 578 n_atoms = self.get_number_of_atoms() 579 if len(atomic_numbers) != n_atoms: 580 raise ValueError( 581 f"Expected {n_atoms} atomic numbers, got {len(atomic_numbers)}" 582 ) 583 for i, num in enumerate(atomic_numbers): 584 self.molecule.GetAtom(i).SetAtomicNumber(num) 585 586 # Rendering customization methods 587 def use_ball_and_stick(self): 588 """Set the molecule to use ball-and-stick representation.""" 589 self.mapper.UseBallAndStickSettings() 590 return self 591 592 def use_space_filling(self): 593 """Set the molecule to use space-filling (VDW spheres) representation.""" 594 self.mapper.UseVDWSpheresSettings() 595 return self 596 597 def set_atom_radius_scale(self, scale): 598 """Set the scale factor for atom radii. 599 600 Arguments: 601 scale : (float) 602 Scaling factor for atom spheres. 603 """ 604 self.mapper.SetAtomicRadiusScaleFactor(scale) 605 return self 606 607 def set_bond_radius(self, radius): 608 """Set the radius of bonds. 609 610 Arguments: 611 radius : (float) 612 Bond radius in world units. 613 """ 614 self.mapper.SetBondRadius(radius) 615 return self
332 def __init__(self, pdb_file=None): 333 # Create an empty molecule 334 self.molecule = vtkMolecule() 335 336 # Configure the mapper and actor for rendering 337 self.mapper = vtkMoleculeMapper() 338 self.actor = vtkActor() 339 self.property = self.actor.GetProperty() 340 341 if pdb_file: 342 # Create and configure the PDB reader 343 reader = vtkPDBReader() 344 reader.SetFileName(pdb_file) 345 reader.Update() 346 347 # Get the PDB data 348 pdb_data = reader.GetOutput() 349 # print the point data available 350 # print(pdb_data.GetPointData()) 351 # Array 0 name = atom_type 352 # Array 1 name = atom_types 353 # Array 2 name = residue 354 # Array 3 name = chain 355 # Array 4 name = secondary_structures 356 # Array 5 name = secondary_structures_begin 357 # Array 6 name = secondary_structures_end 358 # Array 7 name = ishetatm 359 # Array 8 name = model 360 # Array 9 name = rgb_colors 361 # Array 10 name = radius 362 # for i in range(pdb_data.GetPointData().GetNumberOfArrays()): 363 # print(pdb_data.GetPointData().GetArray(i)) 364 points = pdb_data.GetPoints() 365 point_data = pdb_data.GetPointData() 366 367 # Extract atom information and add to molecule 368 for i in range(points.GetNumberOfPoints()): 369 position = points.GetPoint(i) 370 # Default to Carbon if atomic number is not available 371 atomic_number = 6 372 if point_data.GetScalars("atom_type"): 373 atomic_number = int(point_data.GetScalars("atom_type").GetValue(i)) 374 # Add atom to molecule 375 # if point_data.GetScalars("rgb_colors"): 376 # color = point_data.GetScalars("rgb_colors").GetTuple3(i) 377 # self.actor.GetProperty().SetColor(color) 378 self.molecule.AppendAtom(atomic_number, position) 379 380 # Add bonds if available 381 if pdb_data.GetLines(): 382 lines = pdb_data.GetLines() 383 lines.InitTraversal() 384 id_list = vtkIdList() 385 while lines.GetNextCell(id_list): 386 if id_list.GetNumberOfIds() == 2: 387 self.molecule.AppendBond( 388 id_list.GetId(0), 389 id_list.GetId(1), 390 1, # Default to single bond 391 ) 392 393 # Set the molecule as input to the mapper 394 self.mapper.SetInputData(self.molecule) 395 self.actor.SetMapper(self.mapper) 396 397 # Apply default rendering style 398 self.use_ball_and_stick()
400 def append_atom(self, position=None, atomic_number=6): 401 """Add an atom to the molecule with optional position and atomic number. 402 403 Arguments: 404 position: (list or np.ndarray) 405 [x, y, z] coordinates. Defaults to [0, 0, 0]. 406 atomic_number : (int) 407 Atomic number (e.g., 6 for Carbon). Defaults to 6. 408 409 Returns: 410 The added atom object. 411 """ 412 if position is None: 413 position = [0, 0, 0] 414 pos = np.asarray(position, dtype=float) 415 vtk_atom = self.molecule.AppendAtom(atomic_number, pos[0], pos[1], pos[2]) 416 atom_id = ( 417 self.molecule.GetNumberOfAtoms() - 1 418 ) # The ID will be the index of the last added atom 419 return Atom(self.molecule, atom_id)
Add an atom to the molecule with optional position and atomic number.
Arguments:
- position: (list or np.ndarray) [x, y, z] coordinates. Defaults to [0, 0, 0].
- atomic_number : (int) Atomic number (e.g., 6 for Carbon). Defaults to 6.
Returns:
The added atom object.
421 def get_atom(self, atom_id): 422 """Retrieve an atom by its ID. 423 424 Arguments: 425 atom_id : (int) 426 The ID of the atom. 427 428 Returns: 429 The atom object. 430 """ 431 if atom_id >= self.get_number_of_atoms(): 432 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 433 return Atom(self.molecule, atom_id)
Retrieve an atom by its ID.
Arguments:
- atom_id : (int) The ID of the atom.
Returns:
The atom object.
435 def remove_atom(self, atom_id): 436 """Remove an atom by its ID. 437 438 Arguments: 439 atom_id (int): The ID of the atom to remove. 440 """ 441 if atom_id >= self.get_number_of_atoms(): 442 raise ValueError(f"Atom ID {atom_id} exceeds number of atoms.") 443 self.molecule.RemoveAtom(atom_id) 444 # Update the mapper to reflect the changes 445 self.mapper.SetInputData(self.molecule) 446 self.mapper.Update() 447 # Update the actor to reflect the changes 448 self.actor.SetMapper(self.mapper) 449 self.actor.Update() 450 # Update the property to reflect the changes 451 self.property.SetColor(1.0, 0.8, 0.6) # Reset to default color 452 self.property.SetOpacity(1.0)
Remove an atom by its ID.
Arguments:
- atom_id (int): The ID of the atom to remove.
454 def get_array(self, name): 455 """Get a point data array by name. 456 457 The following arrays are available: 458 459 - atom_type: Atomic number of the atom. 460 - atom_types: Atomic type of the atom. 461 - residue: Residue name. 462 - chain: Chain identifier. 463 - secondary_structures: Secondary structure type. 464 - secondary_structures_begin: Start index of the secondary structure. 465 - secondary_structures_end: End index of the secondary structure. 466 - ishetatm: Is the atom a heteroatom? 467 - model: Model number. 468 - rgb_colors: RGB color of the atom. 469 - radius: Radius of the atom. 470 471 Arguments: 472 name : (str) 473 The name of the array. 474 475 Returns: 476 The array data. 477 """ 478 if not self.molecule.GetPointData().HasArray(name): 479 raise ValueError(f"Array '{name}' not found in molecule.") 480 return vtk_to_numpy(self.molecule.GetPointData().GetArray(name))
Get a point data array by name.
The following arrays are available:
- atom_type: Atomic number of the atom.
- atom_types: Atomic type of the atom.
- residue: Residue name.
- chain: Chain identifier.
- secondary_structures: Secondary structure type.
- secondary_structures_begin: Start index of the secondary structure.
- secondary_structures_end: End index of the secondary structure.
- ishetatm: Is the atom a heteroatom?
- model: Model number.
- rgb_colors: RGB color of the atom.
- radius: Radius of the atom.
Arguments:
- name : (str) The name of the array.
Returns:
The array data.
482 def append_bond(self, atom1, atom2, order=1): 483 """Add a bond between two atoms. 484 485 Arguments: 486 atom1 : (Atom or int) 487 The first atom or its ID. 488 atom2 : (Atom or int) 489 The second atom or its ID. 490 order : (int) 491 Bond order (1=single, 2=double, 3=triple). 492 """ 493 atom1_id = atom1.atom_id if isinstance(atom1, Atom) else atom1 494 atom2_id = atom2.atom_id if isinstance(atom2, Atom) else atom2 495 self.molecule.AppendBond(atom1_id, atom2_id, order)
Add a bond between two atoms.
Arguments:
- atom1 : (Atom or int) The first atom or its ID.
- atom2 : (Atom or int) The second atom or its ID.
- order : (int) Bond order (1=single, 2=double, 3=triple).
497 def get_number_of_atoms(self): 498 """Get the number of atoms in the molecule. 499 500 Returns: 501 Number of atoms. 502 """ 503 return self.molecule.GetNumberOfAtoms()
Get the number of atoms in the molecule.
Returns:
Number of atoms.
505 def get_number_of_bonds(self): 506 """Get the number of bonds in the molecule. 507 508 Returns: 509 Number of bonds. 510 """ 511 return self.molecule.GetNumberOfBonds()
Get the number of bonds in the molecule.
Returns:
Number of bonds.
513 def get_bond(self, bond_id): 514 """Get bond information by ID (simplified as VTK bond access is limited). 515 516 Arguments: 517 bond_id : (int) 518 The ID of the bond. 519 520 Returns: 521 (atom1_id, atom2_id, order). 522 """ 523 if bond_id >= self.get_number_of_bonds(): 524 raise ValueError(f"Bond ID {bond_id} exceeds number of bonds.") 525 bond = self.molecule.GetBond(bond_id) 526 return (bond.GetBeginAtomId(), bond.GetEndAtomId(), bond.GetOrder())
Get bond information by ID (simplified as VTK bond access is limited).
Arguments:
- bond_id : (int) The ID of the bond.
Returns:
(atom1_id, atom2_id, order).
528 def get_atom_positions(self): 529 """Get the positions of all atoms. 530 531 Returns: 532 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 533 """ 534 n_atoms = self.get_number_of_atoms() 535 positions = np.zeros((n_atoms, 3)) 536 for i in range(n_atoms): 537 pos = self.molecule.GetAtom(i).GetPosition() 538 positions[i] = [pos[0], pos[1], pos[2]] 539 return positions
Get the positions of all atoms.
Returns:
Array of shape (n_atoms, 3) with [x, y, z] coordinates.
541 def set_atom_positions(self, positions): 542 """ 543 Set the positions of all atoms. 544 545 Arguments: 546 positions : (np.ndarray) 547 Array of shape (n_atoms, 3) with [x, y, z] coordinates. 548 """ 549 n_atoms = self.get_number_of_atoms() 550 positions = np.asarray(positions, dtype=float) 551 if positions.shape != (n_atoms, 3): 552 raise ValueError( 553 f"Expected positions shape ({n_atoms}, 3), got {positions.shape}" 554 ) 555 for i in range(n_atoms): 556 self.molecule.GetAtom(i).SetPosition(positions[i])
Set the positions of all atoms.
Arguments:
- positions : (np.ndarray) Array of shape (n_atoms, 3) with [x, y, z] coordinates.
558 def get_atomic_numbers(self): 559 """ 560 Get the atomic numbers of all atoms. 561 562 Returns: 563 List of atomic numbers. 564 """ 565 return [ 566 self.molecule.GetAtom(i).GetAtomicNumber() 567 for i in range(self.get_number_of_atoms()) 568 ]
Get the atomic numbers of all atoms.
Returns:
List of atomic numbers.
570 def set_atomic_numbers(self, atomic_numbers): 571 """ 572 Set the atomic numbers of all atoms. 573 574 Arguments: 575 atomic_numbers : (list) 576 List of atomic numbers. 577 """ 578 n_atoms = self.get_number_of_atoms() 579 if len(atomic_numbers) != n_atoms: 580 raise ValueError( 581 f"Expected {n_atoms} atomic numbers, got {len(atomic_numbers)}" 582 ) 583 for i, num in enumerate(atomic_numbers): 584 self.molecule.GetAtom(i).SetAtomicNumber(num)
Set the atomic numbers of all atoms.
Arguments:
- atomic_numbers : (list) List of atomic numbers.
587 def use_ball_and_stick(self): 588 """Set the molecule to use ball-and-stick representation.""" 589 self.mapper.UseBallAndStickSettings() 590 return self
Set the molecule to use ball-and-stick representation.
592 def use_space_filling(self): 593 """Set the molecule to use space-filling (VDW spheres) representation.""" 594 self.mapper.UseVDWSpheresSettings() 595 return self
Set the molecule to use space-filling (VDW spheres) representation.
597 def set_atom_radius_scale(self, scale): 598 """Set the scale factor for atom radii. 599 600 Arguments: 601 scale : (float) 602 Scaling factor for atom spheres. 603 """ 604 self.mapper.SetAtomicRadiusScaleFactor(scale) 605 return self
Set the scale factor for atom radii.
Arguments:
- scale : (float) Scaling factor for atom spheres.
607 def set_bond_radius(self, radius): 608 """Set the radius of bonds. 609 610 Arguments: 611 radius : (float) 612 Bond radius in world units. 613 """ 614 self.mapper.SetBondRadius(radius) 615 return self
Set the radius of bonds.
Arguments:
- radius : (float) Bond radius in world units.
618class Protein: 619 """ 620 A Vedo-compatible class for protein ribbon visualization, wrapping vtkProteinRibbonFilter. 621 622 This class generates a ribbon representation of protein structures from PDB files, 623 vtkMolecule objects, or vtkPolyData, and integrates with Vedo's rendering system. 624 625 Attributes: 626 filter (vtkProteinRibbonFilter): The underlying VTK filter for ribbon generation. 627 mapper (vtkPolyDataMapper): Maps the filter's output to renderable data. 628 actor (vtkActor): The VTK actor for rendering the ribbon. 629 """ 630 631 def __init__(self, input_data): 632 """ 633 Initialize the ProteinRibbon with input data. 634 635 Arguments: 636 input_data : (str or vtkMolecule or vtkPolyData) 637 - Path to a PDB file (str) 638 - A vtkMolecule object 639 - A vtkPolyData object 640 641 Raises: 642 `ValueError` If the input_data type is not supported. 643 """ 644 645 # Handle different input types 646 if isinstance(input_data, str): 647 # Read PDB file using vtkPDBReader 648 reader = vtkPDBReader() 649 reader.SetFileName(input_data) 650 reader.Update() 651 self.input_data = reader.GetOutput() 652 elif isinstance(input_data, vtkMolecule): 653 self.input_data = input_data 654 elif isinstance(input_data, vtkPolyData): 655 self.input_data = input_data 656 else: 657 raise ValueError( 658 "Input must be a PDB file path, vtkMolecule, or vtkPolyData." 659 ) 660 661 # Create and configure the ribbon filter 662 self.filter = vtkProteinRibbonFilter() 663 self.filter.SetInputData(self.input_data) 664 self.filter.Update() 665 666 # Set up the mapper and actor for rendering 667 self.mapper = vtkPolyDataMapper() 668 self.mapper.SetInputConnection(self.filter.GetOutputPort()) 669 self.actor = vtkActor() 670 self.actor.SetMapper(self.mapper) 671 self.property = self.actor.GetProperty() 672 673 # Set default visual properties 674 self.property.SetColor(1.0, 0.8, 0.6) # Soft peach color 675 self.property.SetOpacity(1.0) 676 677 def set_coil_width(self, width): 678 """ 679 Set the width of the coil regions in the ribbon. 680 681 Arguments: 682 width : (float) 683 The width of the coil regions. 684 """ 685 self.filter.SetCoilWidth(width) 686 self.filter.Update() 687 return self 688 689 def set_helix_width(self, width): 690 """ 691 Set the width of the helix regions in the ribbon. 692 693 Arguments: 694 width : (float) 695 The width of the helix regions. 696 """ 697 self.filter.SetHelixWidth(width) 698 self.filter.Update() 699 return self 700 701 def set_sphere_resolution(self, resolution): 702 """ 703 Set the resolution of spheres used in the ribbon representation. 704 705 Arguments: 706 resolution : (int) 707 The resolution of the spheres. 708 """ 709 self.filter.SetSphereResolution(resolution) 710 self.filter.Update() 711 return self
A Vedo-compatible class for protein ribbon visualization, wrapping vtkProteinRibbonFilter.
This class generates a ribbon representation of protein structures from PDB files, vtkMolecule objects, or vtkPolyData, and integrates with Vedo's rendering system.
Attributes:
- filter (vtkProteinRibbonFilter): The underlying VTK filter for ribbon generation.
- mapper (vtkPolyDataMapper): Maps the filter's output to renderable data.
- actor (vtkActor): The VTK actor for rendering the ribbon.
631 def __init__(self, input_data): 632 """ 633 Initialize the ProteinRibbon with input data. 634 635 Arguments: 636 input_data : (str or vtkMolecule or vtkPolyData) 637 - Path to a PDB file (str) 638 - A vtkMolecule object 639 - A vtkPolyData object 640 641 Raises: 642 `ValueError` If the input_data type is not supported. 643 """ 644 645 # Handle different input types 646 if isinstance(input_data, str): 647 # Read PDB file using vtkPDBReader 648 reader = vtkPDBReader() 649 reader.SetFileName(input_data) 650 reader.Update() 651 self.input_data = reader.GetOutput() 652 elif isinstance(input_data, vtkMolecule): 653 self.input_data = input_data 654 elif isinstance(input_data, vtkPolyData): 655 self.input_data = input_data 656 else: 657 raise ValueError( 658 "Input must be a PDB file path, vtkMolecule, or vtkPolyData." 659 ) 660 661 # Create and configure the ribbon filter 662 self.filter = vtkProteinRibbonFilter() 663 self.filter.SetInputData(self.input_data) 664 self.filter.Update() 665 666 # Set up the mapper and actor for rendering 667 self.mapper = vtkPolyDataMapper() 668 self.mapper.SetInputConnection(self.filter.GetOutputPort()) 669 self.actor = vtkActor() 670 self.actor.SetMapper(self.mapper) 671 self.property = self.actor.GetProperty() 672 673 # Set default visual properties 674 self.property.SetColor(1.0, 0.8, 0.6) # Soft peach color 675 self.property.SetOpacity(1.0)
Initialize the ProteinRibbon with input data.
Arguments:
- input_data : (str or vtkMolecule or vtkPolyData)
- Path to a PDB file (str)
- A vtkMolecule object
- A vtkPolyData object
Raises:
ValueError
If the input_data type is not supported.
677 def set_coil_width(self, width): 678 """ 679 Set the width of the coil regions in the ribbon. 680 681 Arguments: 682 width : (float) 683 The width of the coil regions. 684 """ 685 self.filter.SetCoilWidth(width) 686 self.filter.Update() 687 return self
Set the width of the coil regions in the ribbon.
Arguments:
- width : (float) The width of the coil regions.
689 def set_helix_width(self, width): 690 """ 691 Set the width of the helix regions in the ribbon. 692 693 Arguments: 694 width : (float) 695 The width of the helix regions. 696 """ 697 self.filter.SetHelixWidth(width) 698 self.filter.Update() 699 return self
Set the width of the helix regions in the ribbon.
Arguments:
- width : (float) The width of the helix regions.
701 def set_sphere_resolution(self, resolution): 702 """ 703 Set the resolution of spheres used in the ribbon representation. 704 705 Arguments: 706 resolution : (int) 707 The resolution of the spheres. 708 """ 709 self.filter.SetSphereResolution(resolution) 710 self.filter.Update() 711 return self
Set the resolution of spheres used in the ribbon representation.
Arguments:
- resolution : (int) The resolution of the spheres.