Source code for AtomHighlighter

import bpy
import math

[docs] def hex_to_rgba(hex_color): """ Converts a hex color string to an RGBA list, supporting optional alpha. :param hex_color: (str) A string representing the hex color (e.g., "#ea1517" or "#ea151780"). :return: A list of four float values representing the RGBA color. :rtype: [float, float, float, float] """ hex_color = hex_color.lstrip('#') if len(hex_color) == 6: # No alpha return [int(hex_color[i:i+2], 16) / 255.0 for i in (0, 2, 4)] + [1.0] elif len(hex_color) == 8: # Includes alpha return [int(hex_color[i:i+2], 16) / 255.0 for i in (0, 2, 4, 6)] else: raise ValueError("Invalid hex color format. Use #RRGGBB or #RRGGBBAA.")
[docs] def find_bond_object(atom1, atom2): """ Finds the bond object in the scene that contains atom1 and atom2 in its name. :param atom1: (str) The name and index of the first atom (e.g., "C01"). :param atom2: (str) The name and index of the second atom (e.g., "C02"). :return: The bond object if found, None otherwise. :rtype: bpy.types.Object or None """ separators = ['-', '=', '#', '%', '_'] for obj in bpy.data.objects: for sep in separators: if f"{atom1}{sep}{atom2}" in obj.name or f"{atom2}{sep}{atom1}" in obj.name: return obj return None
[docs] def highlight_atom(atom_name, outline_size=1.5, transparency_value=0.5, outline_color="#15eae333"): """ Highlights an atom by creating a highlight sphere around it. :param atom_name: (str) The name of the atom object to highlight. :param outline_size: (float) The size multiplier for the outline sphere relative to the atom object (default is 1.5). :param transparency_value: (float) The transparency value for the outline material (default is 0.5). :param outline_color: (str) The color of the outline in hex format (default is "#15eae3"). """ # Try to get the object with the name atom_name obj = bpy.data.objects.get(atom_name) if obj is None: print(f"Error: Object with name '{atom_name}' not found.") return # Create the highlight sphere bpy.ops.mesh.primitive_uv_sphere_add(radius=outline_size * obj.dimensions[0] / 2, location=obj.location) highlight_sphere = bpy.context.object highlight_sphere.name = f"{atom_name}*highlight" #naming format with '*' to avoid interverence with animate module bpy.ops.object.shade_smooth() # Apply smooth shading # Make the instantiated object a child of the selected object highlight_sphere.parent = obj highlight_sphere.location = (0, 0, 0) # Create the highlight material mat = create_highlight_material(atom_name, transparency_value, outline_color) # Assign the material to the instantiated object highlight_sphere.data.materials.append(mat)
[docs] def create_highlight_material(object_name, transparency_value=0.5, outline_color="#15eae3"): """ Creates a highlight material with the specified transparency and outline color. :param object_name: (str) The name of the object to highlight. :param transparency_value: (float) The transparency value for the material (default is 0.5). :param outline_color: (str) The color of the outline in hex format (default is "#15eae3"). :return: The created highlight material. :rtype: bpy.types.Material """ mat = bpy.data.materials.new(name=f"{object_name}*highlight") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links # Clear default nodes for node in nodes: nodes.remove(node) # Add necessary nodes output_node = nodes.new(type='ShaderNodeOutputMaterial') principled_node = nodes.new(type='ShaderNodeBsdfPrincipled') # Set base color and transparency base_color = hex_to_rgba(outline_color) base_color[3] = transparency_value # Apply alpha value principled_node.inputs['Base Color'].default_value = base_color principled_node.inputs['Alpha'].default_value = transparency_value # Link nodes links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) # Set transparency settings for proper viewport display mat.blend_method = 'BLEND' mat.use_transparent_shadow = False #mat.shadow_method = 'NONE' #<-- shadow_method was removed in Blender 4.2 mat.use_backface_culling = True return mat
[docs] def highlight_bond(atom_1, atom_2, outline_size=0.33, transparency_value=0.5, outline_color="#15eae3"): """ Highlights a bond by creating a highlight cylinder around it. :param atom_1: (str) The name and index of the first atom (e.g., "C01"). :param atom_2: (str) The name and index of the second atom (e.g., "C02"). :param outline_size: (float) The size multiplier for the outline cylinder relative to the bond (default is 0.33). :param transparency_value: (float) The transparency value for the outline material (default is 0.5). :param outline_color: (str) The color of the outline in hex format (default is "#15eae3"). """ # Find the bond object bond_obj = find_bond_object(atom_1, atom_2) if bond_obj is None: print(f"Error: Bond between '{atom_1}' and '{atom_2}' not found.") return # Get the locations of the atoms loc1 = bpy.data.objects[atom_1].location loc2 = bpy.data.objects[atom_2].location # Calculate bond location and orientation bond_loc = (loc1 + loc2) / 2 bond_orientation = loc2 - loc1 try: phi = math.atan2(bond_orientation.y, bond_orientation.x) except ValueError: phi = math.pi / 2 try: theta = math.acos(bond_orientation.z / bond_orientation.length) except ValueError: theta = 0 # Create the highlight cylinder bpy.ops.mesh.primitive_cylinder_add(radius=outline_size / 2, depth=bond_orientation.length, location=bond_loc) highlight_cylinder = bpy.context.object highlight_cylinder.name = f"{atom_1}*{atom_2}*highlight" bpy.ops.object.shade_smooth() # Apply smooth shading # Set the rotation of the cylinder highlight_cylinder.rotation_euler[1] = theta highlight_cylinder.rotation_euler[2] = phi # Create the highlight material mat = create_highlight_material(f"{atom_1}*{atom_2}", transparency_value, outline_color) # Assign the material to the instantiated object highlight_cylinder.data.materials.append(mat) # Set the parent of the highlight cylinder to the bond object highlight_cylinder.parent = bond_obj # Ensure the highlight cylinder retains its location and rotation highlight_cylinder.matrix_parent_inverse = bond_obj.matrix_world.inverted()
#TO DEBUG #mat_dict = {'C': bpy.data.materials['C'], 'S': bpy.data.materials['S'], 'O': bpy.data.materials['O'], 'H': bpy.data.materials['H'], 'Xx': bpy.data.materials['Xx']} #highlight_atom(atom_name="C01", transparency_value=0.2) #highlight_atom(atom_name="O04", transparency_value=0.2) #highlight_bond(atom_1="C01", atom_2="O04", transparency_value=0.2, outline_size=0.33)