diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 00000000..ef06d34a --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,8 @@ +name: Ruff +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 diff --git a/build.py b/build.py index ae123f55..cc4773d0 100644 --- a/build.py +++ b/build.py @@ -5,19 +5,23 @@ # zips up the template file def zip_template(): # Define the directory and zip file paths - dir_path = 'molecularnodes/assets/template/Molecular Nodes' - zip_file_path = 'molecularnodes/assets/template/Molecular Nodes.zip' + dir_path = "molecularnodes/assets/template/Molecular Nodes" + zip_file_path = "molecularnodes/assets/template/Molecular Nodes.zip" # Create a ZipFile object in write mode - with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + with zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED) as zipf: # Walk the directory tree and add files to the zip file for root, dirs, files in os.walk(dir_path): for file in files: # Get the path of the file file_path = os.path.join(root, file) # Add the file to the zip file - zipf.write(file_path, arcname=os.path.relpath( - file_path, start='molecularnodes/assets/template/')) + zipf.write( + file_path, + arcname=os.path.relpath( + file_path, start="molecularnodes/assets/template/" + ), + ) if __name__ == "__main__": diff --git a/molecularnodes/assets/template/Molecular Nodes.zip b/molecularnodes/assets/template/Molecular Nodes.zip index d38dc396..41c27e62 100644 Binary files a/molecularnodes/assets/template/Molecular Nodes.zip and b/molecularnodes/assets/template/Molecular Nodes.zip differ diff --git a/molecularnodes/blender/bones.py b/molecularnodes/blender/bones.py index d40e8633..e3a9499b 100644 --- a/molecularnodes/blender/bones.py +++ b/molecularnodes/blender/bones.py @@ -11,7 +11,7 @@ def clear_armature(object): object.modifiers.remove(mod) -def add_bones(object, name='armature'): +def add_bones(object, name="armature"): # creates bones and assigns correct weights clear_armature(object) @@ -20,27 +20,25 @@ def add_bones(object, name='armature'): armature = create_bones(bone_positions, chain_ids) for i in range(bone_weights.shape[1]): - group = object.vertex_groups.new(name=f'mn_armature_{i}') + group = object.vertex_groups.new(name=f"mn_armature_{i}") vertex_indices = np.where(bone_weights[:, i] == 1)[0] - group.add(vertex_indices.tolist(), 1, 'ADD') + group.add(vertex_indices.tolist(), 1, "ADD") object.select_set(True) armature.select_set(True) bpy.context.view_layer.objects.active = armature - bpy.ops.object.parent_set(type='ARMATURE') + bpy.ops.object.parent_set(type="ARMATURE") bpy.context.view_layer.objects.active = object - bpy.ops.object.modifier_move_to_index( - 'EXEC_DEFAULT', modifier="Armature", index=0) + bpy.ops.object.modifier_move_to_index("EXEC_DEFAULT", modifier="Armature", index=0) return armature def get_bone_positions(object): - positions, atom_name, chain_id, res_id, sec_struct = [ obj.get_attribute(object, att) - for att in ['position', 'atom_name', 'chain_id', 'res_id', 'sec_struct'] + for att in ["position", "atom_name", "chain_id", "res_id", "sec_struct"] ] is_alpha_carbon = atom_name == 2 @@ -59,17 +57,16 @@ def get_bone_positions(object): def get_bone_weights(object): - print('hello world') - + print("hello world") -def create_bones(positions, chain_ids, name='armature'): - bpy.ops.object.add(type='ARMATURE', enter_editmode=True) +def create_bones(positions, chain_ids, name="armature"): + bpy.ops.object.add(type="ARMATURE", enter_editmode=True) object = bpy.context.active_object object.name = name coll.armature().objects.link(object) armature = object.data - armature.name = f'{name}_frame' + armature.name = f"{name}_frame" arm_name = armature.name bones = [] # add bones @@ -96,7 +93,7 @@ def create_bones(positions, chain_ids, name='armature'): armature.edit_bones.active = armature.edit_bones[bone_a] for bone in [bone_a, bone_b]: armature.edit_bones[bone].select = True - bpy.ops.armature.parent_set(type='CONNECTED') + bpy.ops.armature.parent_set(type="CONNECTED") for bone in [bone_a, bone_b]: armature.edit_bones[bone].select = False bpy.ops.object.editmode_toggle() @@ -105,12 +102,14 @@ def create_bones(positions, chain_ids, name='armature'): class MN_MT_Add_Armature(bpy.types.Operator): - bl_idname = 'mn.add_armature' - bl_label = 'Add Armature' - bl_description = 'Automatically add armature for each amino acid of the structure ' + bl_idname = "mn.add_armature" + bl_label = "Add Armature" + bl_description = ( + "Automatically add armature for each amino acid of the structure " + ) def execute(self, context): object = context.active_object add_bones(bpy.data.objects[object.name], name=object.name) - return {'FINISHED'} + return {"FINISHED"} diff --git a/molecularnodes/blender/coll.py b/molecularnodes/blender/coll.py index f5359bba..950de7d6 100644 --- a/molecularnodes/blender/coll.py +++ b/molecularnodes/blender/coll.py @@ -4,17 +4,17 @@ def mn(): """Return the MolecularNodes Collection - The collection called 'MolecularNodes' inside the Blender scene is returned. If the + The collection called 'MolecularNodes' inside the Blender scene is returned. If the collection does not exist first, it is created. """ - coll = bpy.data.collections.get('MolecularNodes') + coll = bpy.data.collections.get("MolecularNodes") if not coll: - coll = bpy.data.collections.new('MolecularNodes') + coll = bpy.data.collections.new("MolecularNodes") bpy.context.scene.collection.children.link(coll) return coll -def armature(name='MN_armature'): +def armature(name="MN_armature"): coll = bpy.data.collections.get(name) if not coll: coll = bpy.data.collections.new(name) @@ -23,8 +23,7 @@ def armature(name='MN_armature'): def data(suffix=""): - """A collection for storing MN related data objects. - """ + """A collection for storing MN related data objects.""" name = f"MN_data{suffix}" collection = bpy.data.collections.get(name) @@ -33,7 +32,9 @@ def data(suffix=""): mn().children.link(collection) # disable the view of the data collection - bpy.context.view_layer.layer_collection.children['MolecularNodes'].children[name].exclude = True + bpy.context.view_layer.layer_collection.children["MolecularNodes"].children[ + name + ].exclude = True return collection @@ -42,7 +43,7 @@ def frames(name="", parent=None, suffix="_frames"): Args: name (str, optional): Name of the collection for the frames. Defaults to "". - parent (_type_, optional): A blender collection which will become the parent + parent (_type_, optional): A blender collection which will become the parent collection. Defaults to the MolecularNodes collection if None. """ coll_frames = bpy.data.collections.new(name + suffix) diff --git a/molecularnodes/blender/node.py b/molecularnodes/blender/node.py index eaf0ed07..9e1032f9 100644 --- a/molecularnodes/blender/node.py +++ b/molecularnodes/blender/node.py @@ -1,14 +1,10 @@ from abc import ABCMeta -from typing import Optional, Any -import warnings -import time import numpy as np import bpy class Node(metaclass=ABCMeta): def __init__(self, node: bpy.types.Node, chain=[]): - self.node = node self.group = node.id_data self.chain = chain @@ -20,18 +16,15 @@ def location(self): def new(self, name): "Add a new node to the node group." try: - return self.group.nodes.new(f'GeometryNode{name}') + return self.group.nodes.new(f"GeometryNode{name}") except RuntimeError: - return self.group.nodes.new(f'ShaderNode{name}') + return self.group.nodes.new(f"ShaderNode{name}") def link(self, name, linkto=0, linkfrom=0): "Create a new node along in the chain and create a link to it. Return the new node." new_node = self.new(name) new_node.location = self.location + np.array((200, 0)) - self.group.links.new( - self.node.outputs[linkfrom], - new_node.inputs[linkto] - ) + self.group.links.new(self.node.outputs[linkfrom], new_node.inputs[linkto]) return Node(new_node, chain=self.chain + [self]) diff --git a/molecularnodes/blender/nodes.py b/molecularnodes/blender/nodes.py index 84f6531f..75cfe262 100644 --- a/molecularnodes/blender/nodes.py +++ b/molecularnodes/blender/nodes.py @@ -10,59 +10,59 @@ from ..blender import obj socket_types = { - 'BOOLEAN': 'NodeSocketBool', - 'GEOMETRY': 'NodeSocketGeometry', - 'INT': 'NodeSocketInt', - 'MATERIAL': 'NodeSocketMaterial', - 'VECTOR': 'NodeSocketVector', - 'STRING': 'NodeSocketString', - 'VALUE': 'NodeSocketFloat', - 'COLLECTION': 'NodeSocketCollection', - 'TEXTURE': 'NodeSocketTexture', - 'COLOR': 'NodeSocketColor', - 'RGBA': 'NodeSocketColor', - 'IMAGE': 'NodeSocketImage' + "BOOLEAN": "NodeSocketBool", + "GEOMETRY": "NodeSocketGeometry", + "INT": "NodeSocketInt", + "MATERIAL": "NodeSocketMaterial", + "VECTOR": "NodeSocketVector", + "STRING": "NodeSocketString", + "VALUE": "NodeSocketFloat", + "COLLECTION": "NodeSocketCollection", + "TEXTURE": "NodeSocketTexture", + "COLOR": "NodeSocketColor", + "RGBA": "NodeSocketColor", + "IMAGE": "NodeSocketImage", } # current implemented representations styles_mapping = { "presets": "MN_style_presets", - 'preset_1': "MN_style_presets", - 'preset_2': "MN_style_presets", - 'preset_3': "MN_style_presets", - 'preset_4': "MN_style_presets", - 'atoms': 'MN_style_spheres', - 'spheres': 'MN_style_spheres', - 'vdw': 'MN_style_spheres', - 'sphere': 'MN_style_spheres', - 'cartoon': 'MN_style_cartoon', - 'ribbon': 'MN_style_ribbon', - 'surface': 'MN_style_surface', - 'ball_and_stick': 'MN_style_ball_and_stick', - 'ball+stick': 'MN_style_ball_and_stick', - 'oxdna': 'MN_oxdna_style_ribbon', + "preset_1": "MN_style_presets", + "preset_2": "MN_style_presets", + "preset_3": "MN_style_presets", + "preset_4": "MN_style_presets", + "atoms": "MN_style_spheres", + "spheres": "MN_style_spheres", + "vdw": "MN_style_spheres", + "sphere": "MN_style_spheres", + "cartoon": "MN_style_cartoon", + "ribbon": "MN_style_ribbon", + "surface": "MN_style_surface", + "ball_and_stick": "MN_style_ball_and_stick", + "ball+stick": "MN_style_ball_and_stick", + "oxdna": "MN_oxdna_style_ribbon", "density_surface": "MN_density_style_surface", - "density_wire": "MN_density_style_wire" + "density_wire": "MN_density_style_wire", } STYLE_ITEMS = ( - ('presets', 'Presets', 'A pre-made combination of different styles'), + ("presets", "Presets", "A pre-made combination of different styles"), ("spheres", "Spheres", "Space-filling atoms style."), ("surface", "Surface", "Solvent-accsible surface."), ("cartoon", "Cartoon", "Secondary structure cartoons"), ("ribbon", "Ribbon", "Continuous backbone ribbon."), - ("ball_and_stick", "Ball and Stick", "Spheres for atoms, sticks for bonds") + ("ball_and_stick", "Ball and Stick", "Spheres for atoms, sticks for bonds"), ) bpy.types.Scene.MN_import_style = bpy.props.EnumProperty( name="Style", description="Default style for importing molecules.", items=STYLE_ITEMS, - default='spheres' + default="spheres", ) -MN_DATA_FILE = os.path.join(pkg.ADDON_DIR, 'assets', 'MN_data_file.blend') +MN_DATA_FILE = os.path.join(pkg.ADDON_DIR, "assets", "MN_data_file.blend") class NodeGroupCreationError(Exception): @@ -99,41 +99,48 @@ def set_selection(group, node, selection): pos = node.location pos = [pos[0] - 200, pos[1] - 200] selection.location = pos - group.links.new(selection.outputs[0], node.inputs['Selection']) + group.links.new(selection.outputs[0], node.inputs["Selection"]) return selection -def create_debug_group(name='MolecularNodesDebugGroup'): +def create_debug_group(name="MolecularNodesDebugGroup"): group = new_group(name=name, fallback=False) - info = group.nodes.new('GeometryNodeObjectInfo') - group.links.new(info.outputs['Geometry'], - group.nodes['Group Output'].inputs[0]) + info = group.nodes.new("GeometryNodeObjectInfo") + group.links.new(info.outputs["Geometry"], group.nodes["Group Output"].inputs[0]) return group -def add_selection(group, sel_name, input_list, field='chain_id'): +def add_selection(group, sel_name, input_list, field="chain_id"): style = style_node(group) - sel_node = add_custom(group, custom_iswitch( - name='selection', - iter_list=input_list, - field=field, - dtype='BOOLEAN' - ).name) + sel_node = add_custom( + group, + custom_iswitch( + name="selection", iter_list=input_list, field=field, dtype="BOOLEAN" + ).name, + ) set_selection(group, style, sel_node) return sel_node def get_output(group): - return group.nodes[bpy.app.translations.pgettext_data("Group Output",)] + return group.nodes[ + bpy.app.translations.pgettext_data( + "Group Output", + ) + ] def get_input(group): - return group.nodes[bpy.app.translations.pgettext_data("Group Input",)] + return group.nodes[ + bpy.app.translations.pgettext_data( + "Group Input", + ) + ] -def get_mod(object, name='MolecularNodes'): +def get_mod(object, name="MolecularNodes"): node_mod = object.modifiers.get(name) if not node_mod: node_mod = object.modifiers.new(name, "NODES") @@ -143,7 +150,13 @@ def get_mod(object, name='MolecularNodes'): def format_node_name(name): "Formats a node's name for nicer printing." - return name.strip("MN_").replace("_", " ").title().replace('Dna', 'DNA').replace('Topo ', 'Topology ') + return ( + name.strip("MN_") + .replace("_", " ") + .title() + .replace("Dna", "DNA") + .replace("Topo ", "Topology ") + ) def get_nodes_last_output(group): @@ -160,38 +173,38 @@ def previous_node(node): def style_node(group): prev = previous_node(get_output(group)) - is_style_node = ("style" in prev.name) + is_style_node = "style" in prev.name while not is_style_node: print(prev.name) prev = previous_node(prev) - is_style_node = ("style" in prev.name) + is_style_node = "style" in prev.name return prev def get_style_node(object): "Walk back through the primary node connections until you find the first style node" - group = object.modifiers['MolecularNodes'].node_group + group = object.modifiers["MolecularNodes"].node_group return style_node(group) def star_node(group): prev = previous_node(get_output(group)) - is_star_node = ("MN_starfile_instances" in prev.name) + is_star_node = "MN_starfile_instances" in prev.name while not is_star_node: prev = previous_node(prev) - is_star_node = ("MN_starfile_instances" in prev.name) + is_star_node = "MN_starfile_instances" in prev.name return prev def get_star_node(object): "Walk back through the primary node connections until you find the first style node" - group = object.modifiers['MolecularNodes'].node_group + group = object.modifiers["MolecularNodes"].node_group return star_node(group) def get_color_node(object): "Walk back through the primary node connections until you find the first style node" - group = object.modifiers['MolecularNodes'].node_group + group = object.modifiers["MolecularNodes"].node_group for node in group.nodes: if node.name == "MN_color_attribute_random": return node @@ -209,8 +222,8 @@ def insert_last_node(group, node, link_input=True): def realize_instances(obj): - group = obj.modifiers['MolecularNodes'].node_group - realize = group.nodes.new('GeometryNodeRealizeInstances') + group = obj.modifiers["MolecularNodes"].node_group + realize = group.nodes.new("GeometryNodeRealizeInstances") insert_last_node(group, realize) @@ -220,26 +233,25 @@ def append(node_name, link=False): warnings.simplefilter("ignore") if not node or link: bpy.ops.wm.append( - 'EXEC_DEFAULT', - directory=os.path.join(MN_DATA_FILE, 'NodeTree'), + "EXEC_DEFAULT", + directory=os.path.join(MN_DATA_FILE, "NodeTree"), filename=node_name, link=link, - use_recursive=True + use_recursive=True, ) node = bpy.data.node_groups.get(node_name) with warnings.catch_warnings(): warnings.simplefilter("ignore") if not node or link: - node_name_components = node_name.split('_') - if node_name_components[0] == 'MN': - data_file = MN_DATA_FILE[:-6] + '_' + \ - node_name_components[1] + '.blend' + node_name_components = node_name.split("_") + if node_name_components[0] == "MN": + data_file = MN_DATA_FILE[:-6] + "_" + node_name_components[1] + ".blend" bpy.ops.wm.append( - 'EXEC_DEFAULT', - directory=os.path.join(data_file, 'NodeTree'), + "EXEC_DEFAULT", + directory=os.path.join(data_file, "NodeTree"), filename=node_name, link=link, - use_recursive=True + use_recursive=True, ) return bpy.data.node_groups[node_name] @@ -250,15 +262,15 @@ def material_default(): and return that material. """ - mat_name = 'MN Default' + mat_name = "MN Default" mat = bpy.data.materials.get(mat_name) if not mat: - print('appending material') + print("appending material") bpy.ops.wm.append( - directory=os.path.join(MN_DATA_FILE, 'Material'), - filename='MN Default', - link=False + directory=os.path.join(MN_DATA_FILE, "Material"), + filename="MN Default", + link=False, ) return bpy.data.materials[mat_name] @@ -270,7 +282,7 @@ def MN_micrograph_material(): and return that material. """ - mat_name = 'MN_micrograph_material' + mat_name = "MN_micrograph_material" return bpy.data.materials[mat_name] @@ -282,22 +294,24 @@ def new_group(name="Geometry Nodes", geometry=True, fallback=True): return group # create a new group for this particular name and do some initial setup - group = bpy.data.node_groups.new(name, 'GeometryNodeTree') - input_node = group.nodes.new('NodeGroupInput') - output_node = group.nodes.new('NodeGroupOutput') + group = bpy.data.node_groups.new(name, "GeometryNodeTree") + input_node = group.nodes.new("NodeGroupInput") + output_node = group.nodes.new("NodeGroupOutput") input_node.location.x = -200 - input_node.width output_node.location.x = 200 if geometry: group.interface.new_socket( - 'Geometry', in_out='INPUT', socket_type='NodeSocketGeometry') + "Geometry", in_out="INPUT", socket_type="NodeSocketGeometry" + ) group.interface.new_socket( - 'Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry') + "Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry" + ) group.links.new(output_node.inputs[0], input_node.outputs[0]) return group -def assign_material(node, material='default'): - material_socket = node.inputs.get('Material') +def assign_material(node, material="default"): + material_socket = node.inputs.get("Material") if material_socket: if not material: pass @@ -307,18 +321,16 @@ def assign_material(node, material='default'): material_socket.default_value = material -def add_node(node_name, label: str = '', show_options=False, material="default"): +def add_node(node_name, label: str = "", show_options=False, material="default"): # intended to be called upon button press in the node tree prev_context = bpy.context.area.type - bpy.context.area.type = 'NODE_EDITOR' + bpy.context.area.type = "NODE_EDITOR" # actually invoke the operator to add a node to the current node tree # use_transform=True ensures it appears where the user's mouse is and is currently # being moved so the user can place it where they wish bpy.ops.node.add_node( - 'INVOKE_DEFAULT', - type='GeometryNodeGroup', - use_transform=True + "INVOKE_DEFAULT", type="GeometryNodeGroup", use_transform=True ) bpy.context.area.type = prev_context node = bpy.context.active_node @@ -326,7 +338,7 @@ def add_node(node_name, label: str = '', show_options=False, material="default") node.width = 200.0 node.show_options = show_options - if label == '': + if label == "": node.label = format_node_name(node_name) else: node.label = label @@ -343,10 +355,9 @@ def add_custom( width=200, material="default", show_options=False, - link=False + link=False, ): - - node = group.nodes.new('GeometryNodeGroup') + node = group.nodes.new("GeometryNodeGroup") node.node_tree = append(name, link=link) # if there is an input socket called 'Material', assign it to the base MN material @@ -386,7 +397,7 @@ def change_style_node(object, style): group.links.remove(output_link) try: - material = node_style.inputs['Material'].default_value + material = node_style.inputs["Material"].default_value except KeyError: material = None # append the new node tree, and swap out the tree that is used for the group @@ -404,7 +415,7 @@ def change_style_node(object, style): if material: try: - node_style.inputs['Material'].default_value = material + node_style.inputs["Material"].default_value = material except KeyError: # the new node doesn't contain a material slot pass @@ -428,15 +439,15 @@ def create_starting_nodes_starfile(object, n_images=1): node_output = get_output(group) node_input.location = [0, 0] node_output.location = [700, 0] - node_star_instances = add_custom(group, 'MN_starfile_instances', [450, 0]) + node_star_instances = add_custom(group, "MN_starfile_instances", [450, 0]) link(node_star_instances.outputs[0], node_output.inputs[0]) link(node_input.outputs[0], node_star_instances.inputs[0]) # Need to manually set Image input to 1, otherwise it will be 0 (even though default is 1) - node_mod['Input_3'] = 1 + node_mod["Input_3"] = 1 -def create_starting_nodes_density(object, threshold=0.8, style='density_surface'): +def create_starting_nodes_density(object, threshold=0.8, style="density_surface"): # ensure there is a geometry nodes modifier called 'MolecularNodes' that is created and applied to the object mod = get_mod(object) node_name = f"MN_density_{object.name}" @@ -453,13 +464,15 @@ def create_starting_nodes_density(object, threshold=0.8, style='density_surface' node_output.location = [800, 0] node_density = add_custom(group, styles_mapping[style], [400, 0]) - node_density.inputs['Threshold'].default_value = threshold + node_density.inputs["Threshold"].default_value = threshold link(node_input.outputs[0], node_density.inputs[0]) link(node_density.outputs[0], node_output.inputs[0]) -def create_starting_node_tree(object, coll_frames=None, style="spheres", name=None, set_color=True): +def create_starting_node_tree( + object, coll_frames=None, style="spheres", name=None, set_color=True +): """ Create a starting node tree for the inputted object. @@ -504,15 +517,13 @@ def create_starting_node_tree(object, coll_frames=None, style="spheres", name=No # if requested, setup the nodes for generating colors in the node tree if set_color: - node_color_set = add_custom(group, 'MN_color_set', [200, 0]) - node_color_common = add_custom(group, 'MN_color_common', [-50, -150]) - node_random_color = add_custom( - group, 'MN_color_attribute_random', [-300, -150]) - - link(node_input.outputs['Geometry'], node_color_set.inputs[0]) - link(node_random_color.outputs['Color'], - node_color_common.inputs['Carbon']) - link(node_color_common.outputs[0], node_color_set.inputs['Color']) + node_color_set = add_custom(group, "MN_color_set", [200, 0]) + node_color_common = add_custom(group, "MN_color_common", [-50, -150]) + node_random_color = add_custom(group, "MN_color_attribute_random", [-300, -150]) + + link(node_input.outputs["Geometry"], node_color_set.inputs[0]) + link(node_random_color.outputs["Color"], node_color_common.inputs["Carbon"]) + link(node_color_common.outputs[0], node_color_set.inputs["Color"]) link(node_color_set.outputs[0], node_style.inputs[0]) to_animate = node_color_set else: @@ -523,30 +534,29 @@ def create_starting_node_tree(object, coll_frames=None, style="spheres", name=No node_output.location = [1100, 0] node_style.location = [800, 0] - node_animate_frames = add_custom(group, 'MN_animate_frames', [500, 0]) - node_animate = add_custom(group, 'MN_animate_value', [500, -300]) + node_animate_frames = add_custom(group, "MN_animate_frames", [500, 0]) + node_animate = add_custom(group, "MN_animate_value", [500, -300]) - node_animate_frames.inputs['Frames'].default_value = coll_frames - node_animate.inputs['To Max'].default_value = len( - coll_frames.objects) - 1 + node_animate_frames.inputs["Frames"].default_value = coll_frames + node_animate.inputs["To Max"].default_value = len(coll_frames.objects) - 1 link(to_animate.outputs[0], node_animate_frames.inputs[0]) link(node_animate_frames.outputs[0], node_style.inputs[0]) - link(node_animate.outputs[0], node_animate_frames.inputs['Frame']) + link(node_animate.outputs[0], node_animate_frames.inputs["Frame"]) -def combine_join_geometry(group, node_list, output='Geometry', join_offset=300): +def combine_join_geometry(group, node_list, output="Geometry", join_offset=300): link = group.links.new max_x = max([node.location[0] for node in node_list]) - node_to_instances = group.nodes.new('GeometryNodeJoinGeometry') + node_to_instances = group.nodes.new("GeometryNodeJoinGeometry") node_to_instances.location = [int(max_x + join_offset), 0] for node in reversed(node_list): - link(node.outputs[output], node_to_instances.inputs['Geometry']) + link(node.outputs[output], node_to_instances.inputs["Geometry"]) return node_to_instances -def split_geometry_to_instances(name, iter_list=('A', 'B', 'C'), attribute='chain_id'): +def split_geometry_to_instances(name, iter_list=("A", "B", "C"), attribute="chain_id"): """Create a Node to Split Geometry by an Attribute into Instances Splits the inputted geometry into instances, based on an attribute field. By @@ -559,9 +569,9 @@ def split_geometry_to_instances(name, iter_list=('A', 'B', 'C'), attribute='chai node_input = get_input(group) node_output = get_output(group) - named_att = group.nodes.new('GeometryNodeInputNamedAttribute') + named_att = group.nodes.new("GeometryNodeInputNamedAttribute") named_att.location = [-200, -200] - named_att.data_type = 'INT' + named_att.data_type = "INT" named_att.inputs[0].default_value = attribute link = group.links.new @@ -570,14 +580,14 @@ def split_geometry_to_instances(name, iter_list=('A', 'B', 'C'), attribute='chai for i, chain in enumerate(iter_list): pos = [i % 10, math.floor(i / 10)] - node_split = add_custom(group, '.MN_utils_split_instance') + node_split = add_custom(group, ".MN_utils_split_instance") node_split.location = [int(250 * pos[0]), int(-300 * pos[1])] - node_split.inputs['Group ID'].default_value = i - link(named_att.outputs['Attribute'], node_split.inputs['Field']) - link(node_input.outputs['Geometry'], node_split.inputs['Geometry']) + node_split.inputs["Group ID"].default_value = i + link(named_att.outputs["Attribute"], node_split.inputs["Field"]) + link(node_input.outputs["Geometry"], node_split.inputs["Geometry"]) list_sep.append(node_split) - node_instance = combine_join_geometry(group, list_sep, 'Instance') + node_instance = combine_join_geometry(group, list_sep, "Instance") node_output.location = [int(10 * 250 + 400), 0] link(node_instance.outputs[0], node_output.inputs[0]) return group @@ -588,16 +598,12 @@ def assembly_initialise(mol: bpy.types.Object): Setup the required data object and nodes for building an assembly. """ - transforms = utils.array_quaternions_from_dict( - mol['biological_assemblies']) + transforms = utils.array_quaternions_from_dict(mol["biological_assemblies"]) data_object = obj.create_data_object( - array=transforms, - name=f"data_assembly_{mol.name}" + array=transforms, name=f"data_assembly_{mol.name}" ) tree_assembly = create_assembly_node_tree( - name=mol.name, - iter_list=mol['chain_ids'], - data_object=data_object + name=mol.name, iter_list=mol["chain_ids"], data_object=data_object ) return tree_assembly @@ -614,49 +620,59 @@ def assembly_insert(mol: bpy.types.Object): def create_assembly_node_tree(name, iter_list, data_object): - node_group_name = f"MN_assembly_{name}" group = new_group(name=node_group_name) link = group.links.new - n_assemblies = len( - np.unique(obj.get_attribute(data_object, 'assembly_id'))) + n_assemblies = len(np.unique(obj.get_attribute(data_object, "assembly_id"))) node_group_instances = split_geometry_to_instances( - name=f".MN_utils_split_{name}", - iter_list=iter_list, - attribute='chain_id' + name=f".MN_utils_split_{name}", iter_list=iter_list, attribute="chain_id" ) - node_group_assembly_instance = append('.MN_assembly_instance_chains') + node_group_assembly_instance = append(".MN_assembly_instance_chains") node_instances = add_custom(group, node_group_instances.name, [0, 0]) - node_assembly = add_custom( - group, node_group_assembly_instance.name, [200, 0]) - node_assembly.inputs['data_object'].default_value = data_object + node_assembly = add_custom(group, node_group_assembly_instance.name, [200, 0]) + node_assembly.inputs["data_object"].default_value = data_object out_sockets = outputs(group) out_sockets[list(out_sockets)[0]].name = "Instances" socket_info = ( - {"name": "Rotation", "type": "NodeSocketFloat", - "min": 0, "max": 1, "default": 1}, - {"name": "Translation", "type": "NodeSocketFloat", - "min": 0, "max": 1, "default": 1}, - {"name": "assembly_id", "type": "NodeSocketInt", - "min": 1, "max": n_assemblies, "default": 1} + { + "name": "Rotation", + "type": "NodeSocketFloat", + "min": 0, + "max": 1, + "default": 1, + }, + { + "name": "Translation", + "type": "NodeSocketFloat", + "min": 0, + "max": 1, + "default": 1, + }, + { + "name": "assembly_id", + "type": "NodeSocketInt", + "min": 1, + "max": n_assemblies, + "default": 1, + }, ) for info in socket_info: - socket = group.interface.items_tree.get(info['name']) + socket = group.interface.items_tree.get(info["name"]) if not socket: socket = group.interface.new_socket( - info['name'], in_out='INPUT', socket_type=info['type']) - socket.default_value = info['default'] - socket.min_value = info['min'] - socket.max_value = info['max'] + info["name"], in_out="INPUT", socket_type=info["type"] + ) + socket.default_value = info["default"] + socket.min_value = info["min"] + socket.max_value = info["max"] - link(get_input(group).outputs[info['name']], - node_assembly.inputs[info['name']]) + link(get_input(group).outputs[info["name"]], node_assembly.inputs[info["name"]]) get_output(group).location = [400, 0] link(get_input(group).outputs[0], node_instances.inputs[0]) @@ -668,9 +684,10 @@ def create_assembly_node_tree(name, iter_list, data_object): def add_inverse_selection(group): output = get_output(group) - if 'Inverted' not in output.inputs.keys(): + if "Inverted" not in output.inputs.keys(): group.interface.new_socket( - 'Inverted', in_out='OUTPUT', socket_type='NodeSocketBool') + "Inverted", in_out="OUTPUT", socket_type="NodeSocketBool" + ) loc = output.location bool_math = group.nodes.new("FunctionNodeBooleanMath") @@ -678,27 +695,28 @@ def add_inverse_selection(group): bool_math.operation = "NOT" group.links.new( - output.inputs['Selection'].links[0].from_socket, bool_math.inputs[0]) - group.links.new(bool_math.outputs[0], output.inputs['Inverted']) + output.inputs["Selection"].links[0].from_socket, bool_math.inputs[0] + ) + group.links.new(bool_math.outputs[0], output.inputs["Inverted"]) def custom_iswitch( - name, - iter_list, - field='chain_id', - dtype='BOOLEAN', - default_values=None, - prefix='', - start=0 + name, + iter_list, + field="chain_id", + dtype="BOOLEAN", + default_values=None, + prefix="", + start=0, ): """ - Creates a named `Index Switch` node. + Creates a named `Index Switch` node. Wraps an index switch node, giving the group names or each name in the `iter_list`. Uses the given field for the attribute name to use in the index switch, and optionally adds an offset value if the start value is non zero. - If a list of default items is given, then it is recycled to fill the defaults for + If a list of default items is given, then it is recycled to fill the defaults for each created socket in for the node. Parameters @@ -740,48 +758,38 @@ def custom_iswitch( link = group.links.new node_input = get_input(group) node_output = get_output(group) - node_attr = group.nodes.new('GeometryNodeInputNamedAttribute') - node_attr.data_type = 'INT' + node_attr = group.nodes.new("GeometryNodeInputNamedAttribute") + node_attr.data_type = "INT" node_attr.location = [0, 150] - node_attr.inputs['Name'].default_value = str(field) + node_attr.inputs["Name"].default_value = str(field) - node_iswitch = group.nodes.new('GeometryNodeIndexSwitch') + node_iswitch = group.nodes.new("GeometryNodeIndexSwitch") node_iswitch.data_type = dtype - link(node_attr.outputs['Attribute'], node_iswitch.inputs['Index']) + link(node_attr.outputs["Attribute"], node_iswitch.inputs["Index"]) # if there is as offset to the lookup values (say we want to start looking up # from 100 or 1000 etc) then we add a math node with that offset value if start != 0: - node_math = group.nodes.new('ShaderNodeMath') - node_math.operation = 'ADD' + node_math = group.nodes.new("ShaderNodeMath") + node_math.operation = "ADD" node_math.location = [0, 150] node_attr.location = [0, 300] node_math.inputs[1].default_value = start - link( - node_attr.outputs['Attribute'], - node_math.inputs[0] - ) - link( - node_math.outputs['Value'], - node_iswitch.inputs['Index'] - ) + link(node_attr.outputs["Attribute"], node_math.inputs[0]) + link(node_math.outputs["Value"], node_iswitch.inputs["Index"]) # if there are custom values provided, create a dictionary lookup for those values # to assign to the sockets upon creation. If no default was given and the dtype # is colors, then generate a random pastel color for each value default_lookup = None if default_values: - default_lookup = dict(zip( - iter_list, - itertools.cycle(default_values) - )) - elif dtype == 'RGBA': - default_lookup = dict(zip( - iter_list, - [color.random_rgb() for i in iter_list] - )) + default_lookup = dict(zip(iter_list, itertools.cycle(default_values))) + elif dtype == "RGBA": + default_lookup = dict( + zip(iter_list, [color.random_rgb() for i in iter_list]) + ) # for each item in the iter_list, we create a new socket on the interface for this # node group, and link it to the interface on the index switch. The index switch @@ -792,29 +800,19 @@ def custom_iswitch( node_iswitch.index_switch_items.new() socket = group.interface.new_socket( - name=f'{prefix}{item}', - in_out='INPUT', - socket_type=socket_type + name=f"{prefix}{item}", in_out="INPUT", socket_type=socket_type ) # if a set of default values was given, then use it for setting # the defaults on the created sockets of the node group if default_lookup: socket.default_value = default_lookup[item] - link( - node_input.outputs[socket.identifier], - node_iswitch.inputs[str(i)] - ) + link(node_input.outputs[socket.identifier], node_iswitch.inputs[str(i)]) socket_out = group.interface.new_socket( - name='Color', - in_out='OUTPUT', - socket_type=socket_type - ) - link( - node_iswitch.outputs['Output'], - node_output.inputs[socket_out.identifier] + name="Color", in_out="OUTPUT", socket_type=socket_type ) + link(node_iswitch.outputs["Output"], node_output.inputs[socket_out.identifier]) return group @@ -823,7 +821,7 @@ def custom_iswitch( node_name = group.name bpy.data.node_groups.remove(group) raise NodeGroupCreationError( - f'Unable to make node group: {node_name}.\nError: {e}' + f"Unable to make node group: {node_name}.\nError: {e}" ) @@ -839,16 +837,16 @@ def resid_multiple_selection(node_name, input_resid_string): # do a cleanning of input string to allow fuzzy input from users for c in ";/+ .": if c in input_resid_string: - input_resid_string = input_resid_string.replace(c, ',') + input_resid_string = input_resid_string.replace(c, ",") for c in "_=:": if c in input_resid_string: - input_resid_string = input_resid_string.replace(c, '-') + input_resid_string = input_resid_string.replace(c, "-") # print(f'fixed input:{input_resid_string}') # parse input_resid_string into sub selecting string list - sub_list = [item for item in input_resid_string.split(',') if item] + sub_list = [item for item in input_resid_string.split(",") if item] # distance vertical to space all of the created nodes node_sep_dis = -100 @@ -860,55 +858,54 @@ def resid_multiple_selection(node_name, input_resid_string): # also create the required group node input and position it residue_id_group = bpy.data.node_groups.new(node_name, "GeometryNodeTree") node_input = residue_id_group.nodes.new("NodeGroupInput") - node_input.location = [0, node_sep_dis * len(sub_list)/2] + node_input.location = [0, node_sep_dis * len(sub_list) / 2] group_link = residue_id_group.links.new new_node = residue_id_group.nodes.new prev = None for residue_id_index, residue_id in enumerate(sub_list): - # add an new node of Select Res ID or MN_sek_res_id_range current_node = new_node("GeometryNodeGroup") # add an bool_math block bool_math = new_node("FunctionNodeBooleanMath") - bool_math.location = [400, (residue_id_index+1) * node_sep_dis] + bool_math.location = [400, (residue_id_index + 1) * node_sep_dis] bool_math.operation = "OR" - if '-' in residue_id: + if "-" in residue_id: # set two new inputs - current_node.node_tree = append('MN_select_res_id_range') - [resid_start, resid_end] = residue_id.split('-')[:2] + current_node.node_tree = append("MN_select_res_id_range") + [resid_start, resid_end] = residue_id.split("-")[:2] socket_1 = residue_id_group.interface.new_socket( - 'res_id: Min', in_out='INPUT', socket_type='NodeSocketInt') + "res_id: Min", in_out="INPUT", socket_type="NodeSocketInt" + ) socket_1.default_value = int(resid_start) socket_2 = residue_id_group.interface.new_socket( - 'res_id: Max', in_out='INPUT', socket_type='NodeSocketInt') + "res_id: Max", in_out="INPUT", socket_type="NodeSocketInt" + ) socket_2.default_value = int(resid_end) # a residue range - group_link( - node_input.outputs[socket_1.identifier], current_node.inputs[0]) - group_link( - node_input.outputs[socket_2.identifier], current_node.inputs[1]) + group_link(node_input.outputs[socket_1.identifier], current_node.inputs[0]) + group_link(node_input.outputs[socket_2.identifier], current_node.inputs[1]) else: # create a node - current_node.node_tree = append('MN_select_res_id_single') + current_node.node_tree = append("MN_select_res_id_single") socket = residue_id_group.interface.new_socket( - 'res_id', in_out='INPUT', socket_type='NodeSocketInt') + "res_id", in_out="INPUT", socket_type="NodeSocketInt" + ) socket.default_value = int(residue_id) - group_link( - node_input.outputs[socket.identifier], current_node.inputs[0]) + group_link(node_input.outputs[socket.identifier], current_node.inputs[0]) # set the coordinates - current_node.location = [200, (residue_id_index+1) * node_sep_dis] + current_node.location = [200, (residue_id_index + 1) * node_sep_dis] if not prev: # link the first residue selection to the first input of its OR block - group_link(current_node.outputs['Selection'], bool_math.inputs[0]) + group_link(current_node.outputs["Selection"], bool_math.inputs[0]) else: # if it is not the first residue selection, link the output to the previous or block - group_link(current_node.outputs['Selection'], prev.inputs[1]) + group_link(current_node.outputs["Selection"], prev.inputs[1]) # link the ouput of previous OR block to the current OR block group_link(prev.outputs[0], bool_math.inputs[0]) @@ -916,18 +913,17 @@ def resid_multiple_selection(node_name, input_resid_string): # add a output block residue_id_group_out = new_node("NodeGroupOutput") - residue_id_group_out.location = [ - 800, (residue_id_index + 1) / 2 * node_sep_dis] + residue_id_group_out.location = [800, (residue_id_index + 1) / 2 * node_sep_dis] residue_id_group.interface.new_socket( - 'Selection', in_out='OUTPUT', socket_type='NodeSocketBool') + "Selection", in_out="OUTPUT", socket_type="NodeSocketBool" + ) residue_id_group.interface.new_socket( - 'Inverted', in_out='OUTPUT', socket_type='NodeSocketBool') - group_link(prev.outputs[0], residue_id_group_out.inputs['Selection']) + "Inverted", in_out="OUTPUT", socket_type="NodeSocketBool" + ) + group_link(prev.outputs[0], residue_id_group_out.inputs["Selection"]) invert_bool_math = new_node("FunctionNodeBooleanMath") - invert_bool_math.location = [ - 600, (residue_id_index+1) / 3 * 2 * node_sep_dis] + invert_bool_math.location = [600, (residue_id_index + 1) / 3 * 2 * node_sep_dis] invert_bool_math.operation = "NOT" group_link(prev.outputs[0], invert_bool_math.inputs[0]) - group_link(invert_bool_math.outputs[0], - residue_id_group_out.inputs['Inverted']) + group_link(invert_bool_math.outputs[0], residue_id_group_out.inputs["Inverted"]) return residue_id_group diff --git a/molecularnodes/blender/obj.py b/molecularnodes/blender/obj.py index c67d884e..44c4cc71 100644 --- a/molecularnodes/blender/obj.py +++ b/molecularnodes/blender/obj.py @@ -13,15 +13,18 @@ class AttributeTypeInfo: width: int -TYPES = {key: AttributeTypeInfo(*values) for key, values in { - 'FLOAT_VECTOR': ('vector', float, 3), - 'FLOAT_COLOR': ('color', float, 4), - 'QUATERNION': ('value', float, 4), - 'INT': ('value', int, 1), - 'FLOAT': ('value', float, 1), - 'INT32_2D': ('value', int, 2), - 'BOOLEAN': ('value', bool, 1) -}.items()} +TYPES = { + key: AttributeTypeInfo(*values) + for key, values in { + "FLOAT_VECTOR": ("vector", float, 3), + "FLOAT_COLOR": ("color", float, 4), + "QUATERNION": ("value", float, 4), + "INT": ("value", int, 1), + "FLOAT": ("value", float, 1), + "INT32_2D": ("value", int, 2), + "BOOLEAN": ("value", bool, 1), + }.items() +} class AttributeMismatchError(Exception): @@ -103,11 +106,11 @@ def create_object( vertices: np.ndarray = [], edges: np.ndarray = [], faces: np.ndarray = [], - name: str = 'NewObject', - collection: bpy.types.Collection = None + name: str = "NewObject", + collection: bpy.types.Collection = None, ) -> bpy.types.Object: """ - Create a new Blender object, initialised with locations for each vertex. + Create a new Blender object, initialised with locations for each vertex. If edges and faces are supplied then these are also created on the mesh. @@ -137,11 +140,11 @@ def create_object( if not collection: # Add the object to the scene if no collection is specified - collection = bpy.data.collections['Collection'] + collection = bpy.data.collections["Collection"] collection.objects.link(object) - object['type'] = 'molecule' + object["type"] = "molecule" return object @@ -152,7 +155,7 @@ def set_attribute( data: np.ndarray, type=None, domain="POINT", - overwrite: bool = True + overwrite: bool = True, ) -> bpy.types.Attribute: """ Adds and sets the values of an attribute on the object. @@ -170,7 +173,7 @@ def set_attribute( 'FLOAT_VECTOR', 'FLOAT_COLOR", 'QUATERNION', 'FLOAT', 'INT', 'BOOLEAN' ) domain : str, optional - The domain of the attribute. Defaults to "POINT". Currenlty only ('POINT', 'EDGE', + The domain of the attribute. Defaults to "POINT". Currenlty only ('POINT', 'EDGE', 'FACE') have been tested. overwrite : bool, optional Whether to overwrite an existing attribute with the same name. Defaults to False. @@ -212,8 +215,7 @@ def set_attribute( # the 'foreach_set' requires a 1D array, regardless of the shape of the attribute # it also requires the order to be 'c' or blender might crash!! - attribute.data.foreach_set( - TYPES[type].dname, data.reshape(-1).copy(order='c')) + attribute.data.foreach_set(TYPES[type].dname, data.reshape(-1).copy(order="c")) # The updating of data doesn't work 100% of the time (see: # https://projects.blender.org/blender/blender/issues/118507) so this resetting of a @@ -229,7 +231,9 @@ def set_attribute( return attribute -def get_attribute(object: bpy.types.Object, name='position', evaluate=False) -> np.ndarray: +def get_attribute( + object: bpy.types.Object, name="position", evaluate=False +) -> np.ndarray: """ Get the attribute data from the object. @@ -278,10 +282,7 @@ def get_attribute(object: bpy.types.Object, name='position', evaluate=False) -> return array -def import_vdb( - file: str, - collection: bpy.types.Collection = None -) -> bpy.types.Object: +def import_vdb(file: str, collection: bpy.types.Collection = None) -> bpy.types.Object: """ Imports a VDB file as a Blender volume object, in the MolecularNodes collection. @@ -341,23 +342,19 @@ def evaluate_using_mesh(object): debug = create_object() mod = nodes.get_mod(debug) mod.node_group = nodes.create_debug_group() - mod.node_group.nodes['Object Info'].inputs['Object'].default_value = object + mod.node_group.nodes["Object Info"].inputs["Object"].default_value = object # need to use 'evaluate' otherwise the modifiers won't be taken into account return evaluated(debug) def create_data_object( - array, - collection=None, - name='DataObject', - world_scale=0.01, - fallback=False + array, collection=None, name="DataObject", world_scale=0.01, fallback=False ): # still requires a unique call TODO: figure out why # I think this has to do with the bcif instancing extraction array = np.unique(array) - locations = array['translation'] * world_scale + locations = array["translation"] * world_scale if not collection: collection = coll.data() @@ -365,10 +362,10 @@ def create_data_object( object = create_object(locations, collection=collection, name=name) attributes = [ - ('rotation', 'QUATERNION'), - ('assembly_id', 'INT'), - ('chain_id', 'INT'), - ('transform_id', 'INT') + ("rotation", "QUATERNION"), + ("assembly_id", "INT"), + ("chain_id", "INT"), + ("transform_id", "INT"), ] for column, type in attributes: @@ -381,7 +378,6 @@ def create_data_object( if np.issubdtype(data.dtype, str): data = np.unique(data, return_inverse=True)[1] - set_attribute(object, name=column, data=data, - type=type, domain='POINT') + set_attribute(object, name=column, data=data, type=type, domain="POINT") return object diff --git a/molecularnodes/color.py b/molecularnodes/color.py index 2eacf510..9c1f3a00 100644 --- a/molecularnodes/color.py +++ b/molecularnodes/color.py @@ -4,8 +4,7 @@ def random_rgb(seed=None): - """Random Pastel RGB values - """ + """Random Pastel RGB values""" if seed: random.seed(seed) r, g, b = colorsys.hls_to_rgb(random.random(), 0.6, 0.6) @@ -32,8 +31,7 @@ def equidistant_colors(some_list): colors = [colorsys.hls_to_rgb(hue, 0.6, 0.6) for hue in hues] # Convert RGB to 8-bit integer values - colors = [(int(r * 255), int(g * 255), int(b * 255), 1) - for (r, g, b) in colors] + colors = [(int(r * 255), int(g * 255), int(b * 255), 1) for (r, g, b) in colors] return dict(zip(u, colors)) @@ -48,10 +46,7 @@ def color_chains(atomic_numbers, chain_ids): mask = atomic_numbers == 6 colors = colors_from_elements(atomic_numbers) chain_color_dict = equidistant_colors(chain_ids) - chain_colors = np.array(list(map( - lambda x: chain_color_dict.get(x), - chain_ids - ))) + chain_colors = np.array(list(map(lambda x: chain_color_dict.get(x), chain_ids))) colors[mask] = chain_colors[mask] @@ -59,116 +54,116 @@ def color_chains(atomic_numbers, chain_ids): iupac_colors_rgb = { - "H": (255, 255, 255), # Hydrogen + "H": (255, 255, 255), # Hydrogen "He": (217, 255, 255), # Helium "Li": (204, 128, 255), # Lithium - "Be": (194, 255, 0), # Beryllium - "B": (255, 181, 181), # Boron - "C": (144, 144, 144), # Carbon - "N": (48, 80, 248), # Nitrogen - "O": (255, 13, 13), # Oxygen - "F": (144, 224, 80), # Fluorine + "Be": (194, 255, 0), # Beryllium + "B": (255, 181, 181), # Boron + "C": (144, 144, 144), # Carbon + "N": (48, 80, 248), # Nitrogen + "O": (255, 13, 13), # Oxygen + "F": (144, 224, 80), # Fluorine "Ne": (179, 227, 245), # Neon - "Na": (171, 92, 242), # Sodium - "Mg": (138, 255, 0), # Magnesium + "Na": (171, 92, 242), # Sodium + "Mg": (138, 255, 0), # Magnesium "Al": (191, 166, 166), # Aluminum "Si": (240, 200, 160), # Silicon - "P": (255, 128, 0), # Phosphorus - "S": (255, 255, 48), # Sulfur - "Cl": (31, 240, 31), # Chlorine - "K": (143, 64, 212), # Potassium + "P": (255, 128, 0), # Phosphorus + "S": (255, 255, 48), # Sulfur + "Cl": (31, 240, 31), # Chlorine + "K": (143, 64, 212), # Potassium "Ar": (128, 209, 227), # Argon - "Ca": (61, 255, 0), # Calcium + "Ca": (61, 255, 0), # Calcium "Sc": (230, 230, 230), # Scandium "Ti": (191, 194, 199), # Titanium - "V": (166, 166, 171), # Vanadium + "V": (166, 166, 171), # Vanadium "Cr": (138, 153, 199), # Chromium "Mn": (156, 122, 199), # Manganese - "Fe": (224, 102, 51), # Iron + "Fe": (224, 102, 51), # Iron "Ni": (199, 138, 138), # Nickel "Co": (255, 217, 143), # Cobalt - "Cu": (200, 128, 51), # Copper + "Cu": (200, 128, 51), # Copper "Zn": (125, 128, 176), # Zinc "Ga": (194, 143, 143), # Gallium "Ge": (102, 143, 143), # Germanium "As": (189, 128, 227), # Arsenic - "Se": (255, 161, 0), # Selenium - "Br": (166, 41, 41), # Bromine - "Kr": (92, 184, 209), # Krypton - "Rb": (112, 46, 176), # Rubidium - "Sr": (0, 255, 0), # Strontium - "Y": (148, 255, 255), # Yttrium - "Zr": (148, 224, 224), # Zirconium - "Nb": (115, 194, 201), # Niobium - "Mo": (84, 181, 181), # Molybdenum - "Tc": (59, 158, 158), # Technetium - "Ru": (36, 125, 125), # Ruthenium - "Rh": (10, 125, 140), # Rhodium - "Pd": (0, 105, 133), # Palladium + "Se": (255, 161, 0), # Selenium + "Br": (166, 41, 41), # Bromine + "Kr": (92, 184, 209), # Krypton + "Rb": (112, 46, 176), # Rubidium + "Sr": (0, 255, 0), # Strontium + "Y": (148, 255, 255), # Yttrium + "Zr": (148, 224, 224), # Zirconium + "Nb": (115, 194, 201), # Niobium + "Mo": (84, 181, 181), # Molybdenum + "Tc": (59, 158, 158), # Technetium + "Ru": (36, 125, 125), # Ruthenium + "Rh": (10, 125, 140), # Rhodium + "Pd": (0, 105, 133), # Palladium "Ag": (192, 192, 192), # Silver "Cd": (255, 217, 143), # Cadmium "In": (166, 117, 115), # Indium "Sn": (102, 128, 128), # Tin - "Sb": (158, 99, 181), # Antimony - "Te": (212, 122, 0), # Tellurium - "I": (148, 0, 148), # Iodine - "Xe": (66, 158, 176), # Xenon - "Cs": (87, 23, 143), # Cesium - "Ba": (0, 201, 0), # Barium + "Sb": (158, 99, 181), # Antimony + "Te": (212, 122, 0), # Tellurium + "I": (148, 0, 148), # Iodine + "Xe": (66, 158, 176), # Xenon + "Cs": (87, 23, 143), # Cesium + "Ba": (0, 201, 0), # Barium "La": (112, 212, 255), # Lanthanum "Ce": (255, 255, 199), # Cerium "Pr": (217, 255, 199), # Praseodymium "Nd": (199, 255, 199), # Neodymium "Pm": (163, 255, 199), # Promethium "Sm": (143, 255, 199), # Samarium - "Eu": (97, 255, 199), # Europium - "Gd": (69, 255, 199), # Gadolinium - "Tb": (48, 255, 199), # Terbium - "Dy": (31, 255, 199), # Dysprosium - "Ho": (0, 255, 156), # Holmium - "Er": (0, 230, 117), # Erbium - "Tm": (0, 212, 82), # Thulium - "Yb": (0, 191, 56), # Ytterbium - "Lu": (0, 171, 36), # Lutetium - "Hf": (77, 194, 255), # Hafnium - "Ta": (77, 166, 255), # Tantalum - "W": (33, 148, 214), # Tungsten - "Re": (38, 125, 171), # Rhenium - "Os": (38, 102, 150), # Osmium - "Ir": (23, 84, 135), # Iridium + "Eu": (97, 255, 199), # Europium + "Gd": (69, 255, 199), # Gadolinium + "Tb": (48, 255, 199), # Terbium + "Dy": (31, 255, 199), # Dysprosium + "Ho": (0, 255, 156), # Holmium + "Er": (0, 230, 117), # Erbium + "Tm": (0, 212, 82), # Thulium + "Yb": (0, 191, 56), # Ytterbium + "Lu": (0, 171, 36), # Lutetium + "Hf": (77, 194, 255), # Hafnium + "Ta": (77, 166, 255), # Tantalum + "W": (33, 148, 214), # Tungsten + "Re": (38, 125, 171), # Rhenium + "Os": (38, 102, 150), # Osmium + "Ir": (23, 84, 135), # Iridium "Pt": (208, 208, 224), # Platinum - "Au": (255, 209, 35), # Gold + "Au": (255, 209, 35), # Gold "Hg": (184, 184, 208), # Mercury - "Tl": (166, 84, 77), # Thallium - "Pb": (87, 89, 97), # Lead - "Bi": (158, 79, 181), # Bismuth - "Th": (255, 161, 0), # Thorium - "Pa": (255, 161, 0), # Protactinium - "U": (255, 161, 0), # Uranium - "Np": (255, 161, 0), # Neptunium - "Pu": (255, 161, 0), # Plutonium - "Am": (255, 161, 0), # Americium - "Cm": (255, 161, 0), # Curium - "Bk": (255, 161, 0), # Berkelium - "Cf": (255, 161, 0), # Californium - "Es": (255, 161, 0), # Einsteinium - "Fm": (255, 161, 0), # Fermium - "Md": (255, 161, 0), # Mendelevium - "No": (255, 161, 0), # Nobelium - "Lr": (255, 161, 0), # Lawrencium - "Rf": (204, 0, 89), # Rutherfordium - "Db": (209, 0, 79), # Dubnium - "Sg": (217, 0, 69), # Seaborgium - "Bh": (224, 0, 56), # Bohrium - "Hs": (230, 0, 46), # Hassium - "Mt": (235, 0, 38), # Meitnerium - "Ds": (240, 0, 33), # Darmstadtium - "Rg": (241, 0, 30), # Roentgenium - "Cn": (242, 0, 26), # Copernicium - "Nh": (242, 0, 26), # Nihonium - "Fl": (242, 0, 26), # Flerovium - "Mc": (242, 0, 26), # Moscovium - "Lv": (242, 0, 26), # Livermorium - "Ts": (242, 0, 26), # Tennessine - "Og": (242, 0, 26) # Oganesson + "Tl": (166, 84, 77), # Thallium + "Pb": (87, 89, 97), # Lead + "Bi": (158, 79, 181), # Bismuth + "Th": (255, 161, 0), # Thorium + "Pa": (255, 161, 0), # Protactinium + "U": (255, 161, 0), # Uranium + "Np": (255, 161, 0), # Neptunium + "Pu": (255, 161, 0), # Plutonium + "Am": (255, 161, 0), # Americium + "Cm": (255, 161, 0), # Curium + "Bk": (255, 161, 0), # Berkelium + "Cf": (255, 161, 0), # Californium + "Es": (255, 161, 0), # Einsteinium + "Fm": (255, 161, 0), # Fermium + "Md": (255, 161, 0), # Mendelevium + "No": (255, 161, 0), # Nobelium + "Lr": (255, 161, 0), # Lawrencium + "Rf": (204, 0, 89), # Rutherfordium + "Db": (209, 0, 79), # Dubnium + "Sg": (217, 0, 69), # Seaborgium + "Bh": (224, 0, 56), # Bohrium + "Hs": (230, 0, 46), # Hassium + "Mt": (235, 0, 38), # Meitnerium + "Ds": (240, 0, 33), # Darmstadtium + "Rg": (241, 0, 30), # Roentgenium + "Cn": (242, 0, 26), # Copernicium + "Nh": (242, 0, 26), # Nihonium + "Fl": (242, 0, 26), # Flerovium + "Mc": (242, 0, 26), # Moscovium + "Lv": (242, 0, 26), # Livermorium + "Ts": (242, 0, 26), # Tennessine + "Og": (242, 0, 26), # Oganesson } diff --git a/molecularnodes/data.py b/molecularnodes/data.py index cf361218..f54ebcb1 100644 --- a/molecularnodes/data.py +++ b/molecularnodes/data.py @@ -4,15 +4,15 @@ # of an element to its atomic number etc -# elements dictionary format: +# elements dictionary format: # keys are the element symbol for elements up to Lawrencium (a bit overkill) # values are a subdictionary filled with keys detailing element information -# keys in subdicts: 'atomic_number', 'name', 'vdw_radii', and +# keys in subdicts: 'atomic_number', 'name', 'vdw_radii', and # 'standard_mass' # vdw_radii given in picometres # atomic masses given in daltons -# -# this dict is used to convert element symbol to atomic number, vdw_radii, and +# +# this dict is used to convert element symbol to atomic number, vdw_radii, and # mass values elements = { @@ -20,1227 +20,758 @@ "atomic_number": 1, "vdw_radii": 120, "name": "Hydrogen", - "standard_mass": 1.00794 + "standard_mass": 1.00794, }, "He": { "atomic_number": 2, "vdw_radii": 140, "name": "Helium", - "standard_mass": 4.002602 + "standard_mass": 4.002602, }, "Li": { "atomic_number": 3, "vdw_radii": 182, "name": "Lithium", - "standard_mass": 6.941 + "standard_mass": 6.941, }, "Be": { "atomic_number": 4, "vdw_radii": 153, "name": "Beryllium", - "standard_mass": 9.012182 + "standard_mass": 9.012182, }, "B": { "atomic_number": 5, "vdw_radii": 192, "name": "Boron", - "standard_mass": 10.811 + "standard_mass": 10.811, }, "C": { "atomic_number": 6, "vdw_radii": 170, "name": "Carbon", - "standard_mass": 12.0107 + "standard_mass": 12.0107, }, "N": { "atomic_number": 7, "vdw_radii": 155, "name": "Nitrogen", - "standard_mass": 14.0067 + "standard_mass": 14.0067, }, "O": { "atomic_number": 8, "vdw_radii": 152, "name": "Oxygen", - "standard_mass": 15.9994 + "standard_mass": 15.9994, }, "F": { "atomic_number": 9, "vdw_radii": 147, "name": "Fluorine", - "standard_mass": 18.9984032 + "standard_mass": 18.9984032, }, "Ne": { "atomic_number": 10, "vdw_radii": 154, "name": "Neon", - "standard_mass": 20.1797 + "standard_mass": 20.1797, }, "Na": { "atomic_number": 11, "vdw_radii": 227, "name": "Sodium", - "standard_mass": 22.98977 + "standard_mass": 22.98977, }, "Mg": { "atomic_number": 12, "vdw_radii": 173, "name": "Magnesium", - "standard_mass": 24.305 + "standard_mass": 24.305, }, "Al": { "atomic_number": 13, "vdw_radii": 184, "name": "Aluminium", - "standard_mass": 26.981538 + "standard_mass": 26.981538, }, "Si": { "atomic_number": 14, "vdw_radii": 210, "name": "Silicon", - "standard_mass": 28.0855 + "standard_mass": 28.0855, }, "P": { "atomic_number": 15, "vdw_radii": 180, "name": "Phosphorus", - "standard_mass": 30.973761 + "standard_mass": 30.973761, }, "S": { "atomic_number": 16, "vdw_radii": 180, "name": "Sulfur", - "standard_mass": 32.065 + "standard_mass": 32.065, }, "Cl": { "atomic_number": 17, "vdw_radii": 175, "name": "Chlorine", - "standard_mass": 35.453 + "standard_mass": 35.453, }, "Ar": { "atomic_number": 18, "vdw_radii": 188, "name": "Argon", - "standard_mass": 39.948 + "standard_mass": 39.948, }, "K": { "atomic_number": 19, "vdw_radii": 275, "name": "Potassium", - "standard_mass": 39.0983 + "standard_mass": 39.0983, }, "Ca": { "atomic_number": 20, "vdw_radii": 231, "name": "Calcium", - "standard_mass": 40.078 + "standard_mass": 40.078, }, "Sc": { "atomic_number": 21, "vdw_radii": 211, "name": "Scandium", - "standard_mass": 44.95591 + "standard_mass": 44.95591, }, "Ti": { "atomic_number": 22, "vdw_radii": 147, "name": "Titanium", - "standard_mass": 47.867 + "standard_mass": 47.867, }, "V": { "atomic_number": 23, "vdw_radii": 134, "name": "Vanadium", - "standard_mass": 50.9415 + "standard_mass": 50.9415, }, "Cr": { "atomic_number": 24, "vdw_radii": 128, "name": "Chromium", - "standard_mass": 51.9961 + "standard_mass": 51.9961, }, "Mn": { "atomic_number": 25, "vdw_radii": 127, "name": "Manganese", - "standard_mass": 54.938049 + "standard_mass": 54.938049, }, "Fe": { "atomic_number": 26, "vdw_radii": 126, "name": "Iron", - "standard_mass": 55.845 + "standard_mass": 55.845, }, "Co": { "atomic_number": 27, "vdw_radii": 125, "name": "Cobalt", - "standard_mass": 58.9332 + "standard_mass": 58.9332, }, "Ni": { "atomic_number": 28, "vdw_radii": 163, "name": "Nickel", - "standard_mass": 58.6934 + "standard_mass": 58.6934, }, "Cu": { "atomic_number": 29, "vdw_radii": 140, "name": "Copper", - "standard_mass": 63.546 + "standard_mass": 63.546, }, "Zn": { "atomic_number": 30, "vdw_radii": 139, "name": "Zinc", - "standard_mass": 65.409 + "standard_mass": 65.409, }, "Ga": { "atomic_number": 31, "vdw_radii": 187, "name": "Gallium", - "standard_mass": 69.723 + "standard_mass": 69.723, }, "Ge": { "atomic_number": 32, "vdw_radii": 211, "name": "Germanium", - "standard_mass": 72.64 + "standard_mass": 72.64, }, "As": { "atomic_number": 33, "vdw_radii": 185, "name": "Arsenic", - "standard_mass": 74.9216 + "standard_mass": 74.9216, }, "Se": { "atomic_number": 34, "vdw_radii": 190, "name": "Selenium", - "standard_mass": 78.96 + "standard_mass": 78.96, }, "Br": { "atomic_number": 35, "vdw_radii": 185, "name": "Bromine", - "standard_mass": 79.904 + "standard_mass": 79.904, }, "Kr": { "atomic_number": 36, "vdw_radii": 202, "name": "Krypton", - "standard_mass": 83.798 + "standard_mass": 83.798, }, "Rb": { "atomic_number": 37, "vdw_radii": 303, "name": "Rubidium", - "standard_mass": 85.4678 + "standard_mass": 85.4678, }, "Sr": { "atomic_number": 38, "vdw_radii": 249, "name": "Strontium", - "standard_mass": 87.62 + "standard_mass": 87.62, }, "Y": { "atomic_number": 39, "vdw_radii": 180, "name": "Yttrium", - "standard_mass": 88.90585 + "standard_mass": 88.90585, }, "Zr": { "atomic_number": 40, "vdw_radii": 160, "name": "Zirconium", - "standard_mass": 91.224 + "standard_mass": 91.224, }, "Nb": { "atomic_number": 41, "vdw_radii": 146, "name": "Niobium", - "standard_mass": 92.90638 + "standard_mass": 92.90638, }, "Mo": { "atomic_number": 42, "vdw_radii": 239, "name": "Molybdenum", - "standard_mass": 95.94 + "standard_mass": 95.94, }, "Tc": { "atomic_number": 43, "vdw_radii": 136, "name": "Technetium", - "standard_mass": 98.9062 + "standard_mass": 98.9062, }, "Ru": { "atomic_number": 44, "vdw_radii": 134, "name": "Ruthenium", - "standard_mass": 101.07 + "standard_mass": 101.07, }, "Rh": { "atomic_number": 45, "vdw_radii": 137, "name": "Rhodium", - "standard_mass": 102.9055 + "standard_mass": 102.9055, }, "Pd": { "atomic_number": 46, "vdw_radii": 144, "name": "Palladium", - "standard_mass": 106.42 - }, - "Ag": { - "atomic_number": 47, - "name": "Silver", - "standard_mass": 107.8682 - }, - "Cd": { - "atomic_number": 48, - "name": "Cadmium", - "standard_mass": 112.411 - }, - "In": { - "atomic_number": 49, - "name": "Indium", - "standard_mass": 114.818 - }, - "Sn": { - "atomic_number": 50, - "name": "Tin", - "standard_mass": 118.71 - }, - "Sb": { - "atomic_number": 51, - "name": "Antimony", - "standard_mass": 121.76 - }, - "Te": { - "atomic_number": 52, - "name": "Tellurium", - "standard_mass": 127.6 - }, - "I": { - "atomic_number": 53, - "name": "Iodine", - "standard_mass": 126.90447 - }, - "Xe": { - "atomic_number": 54, - "name": "Xenon", - "standard_mass": 131.293 - }, - "Cs": { - "atomic_number": 55, - "name": "Caesium", - "standard_mass": 132.90545 - }, - "Ba": { - "atomic_number": 56, - "name": "Barium", - "standard_mass": 137.327 - }, - "La": { - "atomic_number": 57, - "name": "Lanthanum", - "standard_mass": 138.9055 - }, - "Ce": { - "atomic_number": 58, - "name": "Cerium", - "standard_mass": 140.116 - }, - "Pr": { - "atomic_number": 59, - "name": "Praseodymium", - "standard_mass": 140.90765 - }, - "Nd": { - "atomic_number": 60, - "name": "Neodymium", - "standard_mass": 144.24 - }, - "Pm": { - "atomic_number": 61, - "name": "Promethium", - "standard_mass": 145 - }, - "Sm": { - "atomic_number": 62, - "name": "Samarium", - "standard_mass": 150.36 - }, - "Eu": { - "atomic_number": 63, - "name": "Europium", - "standard_mass": 151.964 - }, - "Gd": { - "atomic_number": 64, - "name": "Gadolinium", - "standard_mass": 157.25 - }, - "Tb": { - "atomic_number": 65, - "name": "Terbium", - "standard_mass": 158.92534 - }, - "Dy": { - "atomic_number": 66, - "name": "Dysprosium", - "standard_mass": 162.5 - }, - "Ho": { - "atomic_number": 67, - "name": "Holmium", - "standard_mass": 164.93032 - }, - "Er": { - "atomic_number": 68, - "name": "Erbium", - "standard_mass": 167.259 - }, - "Tm": { - "atomic_number": 69, - "name": "Thulium", - "standard_mass": 168.93421 - }, - "Yb": { - "atomic_number": 70, - "name": "Ytterbium", - "standard_mass": 173.04 - }, - "Lu": { - "atomic_number": 71, - "name": "Lutetium", - "standard_mass": 174.967 - }, - "Hf": { - "atomic_number": 72, - "name": "Hafnium", - "standard_mass": 178.49 - }, - "Ta": { - "atomic_number": 73, - "name": "Tantalum", - "standard_mass": 180.9479 - }, - "W": { - "atomic_number": 74, - "name": "Tungsten", - "standard_mass": 183.84 - }, - "Re": { - "atomic_number": 75, - "name": "Rhenium", - "standard_mass": 186.207 - }, - "Os": { - "atomic_number": 76, - "name": "Osmium", - "standard_mass": 190.23 - }, - "Ir": { - "atomic_number": 77, - "name": "Iridium", - "standard_mass": 192.217 - }, - "Pt": { - "atomic_number": 78, - "name": "Platinum", - "standard_mass": 195.078 - }, - "Au": { - "atomic_number": 79, - "name": "Gold", - "standard_mass": 196.96655 - }, - "Hg": { - "atomic_number": 80, - "name": "Mercury", - "standard_mass": 200.59 - }, - "Tl": { - "atomic_number": 81, - "name": "Thallium", - "standard_mass": 204.3833 - }, - "Pb": { - "atomic_number": 82, - "name": "Lead", - "standard_mass": 207.2 - }, - "Bi": { - "atomic_number": 83, - "name": "Bismuth", - "standard_mass": 208.98038 - }, - "Po": { - "atomic_number": 84, - "name": "Polonium", - "standard_mass": 209 - }, - "At": { - "atomic_number": 85, - "name": "Astatine", - "standard_mass": 210 - }, - "Rn": { - "atomic_number": 86, - "name": "Radon", - "standard_mass": 222 - }, - "Fr": { - "atomic_number": 87, - "name": "Francium", - "standard_mass": 223 - }, - "Ra": { - "atomic_number": 88, - "name": "Radium", - "standard_mass": 226.0254 - }, - "Ac": { - "atomic_number": 89, - "name": "Actinium", - "standard_mass": 227 - }, - "Th": { - "atomic_number": 90, - "name": "Thorium", - "standard_mass": 232.0381 - }, - "Pa": { - "atomic_number": 91, - "name": "Protactinium", - "standard_mass": 231.03588 + "standard_mass": 106.42, }, - "U": { - "atomic_number": 92, - "name": "Uranium", - "standard_mass": 238.02891 - }, - "Np": { - "atomic_number": 93, - "name": "Neptunium", - "standard_mass": 237.0482 - }, - "Pu": { - "atomic_number": 94, - "name": "Plutonium", - "standard_mass": 244 - }, - "Am": { - "atomic_number": 95, - "name": "Americium", - "standard_mass": 243 - }, - "Cm": { - "atomic_number": 96, - "name": "Curium", - "standard_mass": 243 - }, - "Bk": { - "atomic_number": 97, - "name": "Berkelium", - "standard_mass": 247 - }, - "Cf": { - "atomic_number": 98, - "name": "Californium", - "standard_mass": 251 - }, - "Es": { - "atomic_number": 99, - "name": "Einsteinium", - "standard_mass": 254 - }, - "Fm": { - "atomic_number": 100, - "name": "Fermium", - "standard_mass": 254 - }, - "Md": { - "atomic_number": 101, - "name": "Mendelevium", - "standard_mass": 258 - }, - "No": { - "atomic_number": 102, - "name": "Nobelium", - "standard_mass": 259 - }, - "Lr": { - "atomic_number": 103, - "name": "Lawrencium", - "standard_mass": 262 - }, - "X": { - "atomic_number": -1, - "name": "Unknown", - "standard_mass": 0 - } + "Ag": {"atomic_number": 47, "name": "Silver", "standard_mass": 107.8682}, + "Cd": {"atomic_number": 48, "name": "Cadmium", "standard_mass": 112.411}, + "In": {"atomic_number": 49, "name": "Indium", "standard_mass": 114.818}, + "Sn": {"atomic_number": 50, "name": "Tin", "standard_mass": 118.71}, + "Sb": {"atomic_number": 51, "name": "Antimony", "standard_mass": 121.76}, + "Te": {"atomic_number": 52, "name": "Tellurium", "standard_mass": 127.6}, + "I": {"atomic_number": 53, "name": "Iodine", "standard_mass": 126.90447}, + "Xe": {"atomic_number": 54, "name": "Xenon", "standard_mass": 131.293}, + "Cs": {"atomic_number": 55, "name": "Caesium", "standard_mass": 132.90545}, + "Ba": {"atomic_number": 56, "name": "Barium", "standard_mass": 137.327}, + "La": {"atomic_number": 57, "name": "Lanthanum", "standard_mass": 138.9055}, + "Ce": {"atomic_number": 58, "name": "Cerium", "standard_mass": 140.116}, + "Pr": {"atomic_number": 59, "name": "Praseodymium", "standard_mass": 140.90765}, + "Nd": {"atomic_number": 60, "name": "Neodymium", "standard_mass": 144.24}, + "Pm": {"atomic_number": 61, "name": "Promethium", "standard_mass": 145}, + "Sm": {"atomic_number": 62, "name": "Samarium", "standard_mass": 150.36}, + "Eu": {"atomic_number": 63, "name": "Europium", "standard_mass": 151.964}, + "Gd": {"atomic_number": 64, "name": "Gadolinium", "standard_mass": 157.25}, + "Tb": {"atomic_number": 65, "name": "Terbium", "standard_mass": 158.92534}, + "Dy": {"atomic_number": 66, "name": "Dysprosium", "standard_mass": 162.5}, + "Ho": {"atomic_number": 67, "name": "Holmium", "standard_mass": 164.93032}, + "Er": {"atomic_number": 68, "name": "Erbium", "standard_mass": 167.259}, + "Tm": {"atomic_number": 69, "name": "Thulium", "standard_mass": 168.93421}, + "Yb": {"atomic_number": 70, "name": "Ytterbium", "standard_mass": 173.04}, + "Lu": {"atomic_number": 71, "name": "Lutetium", "standard_mass": 174.967}, + "Hf": {"atomic_number": 72, "name": "Hafnium", "standard_mass": 178.49}, + "Ta": {"atomic_number": 73, "name": "Tantalum", "standard_mass": 180.9479}, + "W": {"atomic_number": 74, "name": "Tungsten", "standard_mass": 183.84}, + "Re": {"atomic_number": 75, "name": "Rhenium", "standard_mass": 186.207}, + "Os": {"atomic_number": 76, "name": "Osmium", "standard_mass": 190.23}, + "Ir": {"atomic_number": 77, "name": "Iridium", "standard_mass": 192.217}, + "Pt": {"atomic_number": 78, "name": "Platinum", "standard_mass": 195.078}, + "Au": {"atomic_number": 79, "name": "Gold", "standard_mass": 196.96655}, + "Hg": {"atomic_number": 80, "name": "Mercury", "standard_mass": 200.59}, + "Tl": {"atomic_number": 81, "name": "Thallium", "standard_mass": 204.3833}, + "Pb": {"atomic_number": 82, "name": "Lead", "standard_mass": 207.2}, + "Bi": {"atomic_number": 83, "name": "Bismuth", "standard_mass": 208.98038}, + "Po": {"atomic_number": 84, "name": "Polonium", "standard_mass": 209}, + "At": {"atomic_number": 85, "name": "Astatine", "standard_mass": 210}, + "Rn": {"atomic_number": 86, "name": "Radon", "standard_mass": 222}, + "Fr": {"atomic_number": 87, "name": "Francium", "standard_mass": 223}, + "Ra": {"atomic_number": 88, "name": "Radium", "standard_mass": 226.0254}, + "Ac": {"atomic_number": 89, "name": "Actinium", "standard_mass": 227}, + "Th": {"atomic_number": 90, "name": "Thorium", "standard_mass": 232.0381}, + "Pa": {"atomic_number": 91, "name": "Protactinium", "standard_mass": 231.03588}, + "U": {"atomic_number": 92, "name": "Uranium", "standard_mass": 238.02891}, + "Np": {"atomic_number": 93, "name": "Neptunium", "standard_mass": 237.0482}, + "Pu": {"atomic_number": 94, "name": "Plutonium", "standard_mass": 244}, + "Am": {"atomic_number": 95, "name": "Americium", "standard_mass": 243}, + "Cm": {"atomic_number": 96, "name": "Curium", "standard_mass": 243}, + "Bk": {"atomic_number": 97, "name": "Berkelium", "standard_mass": 247}, + "Cf": {"atomic_number": 98, "name": "Californium", "standard_mass": 251}, + "Es": {"atomic_number": 99, "name": "Einsteinium", "standard_mass": 254}, + "Fm": {"atomic_number": 100, "name": "Fermium", "standard_mass": 254}, + "Md": {"atomic_number": 101, "name": "Mendelevium", "standard_mass": 258}, + "No": {"atomic_number": 102, "name": "Nobelium", "standard_mass": 259}, + "Lr": {"atomic_number": 103, "name": "Lawrencium", "standard_mass": 262}, + "X": {"atomic_number": -1, "name": "Unknown", "standard_mass": 0}, } -# elements_by_atomic_number dictionary format: -# keys are integers of the element's atomic number for elements up to +# elements_by_atomic_number dictionary format: +# keys are integers of the element's atomic number for elements up to # Lawrencium (a bit overkill) # values are a subdictionary filled with keys detailing element information -# keys in subdicts: 'element_symbol', 'name', 'standard_mass', and +# keys in subdicts: 'element_symbol', 'name', 'standard_mass', and # 'vdw_radii' # vdw_radii given in picometres # atomic masses given in daltons -# -# this dict is used to back-convert atomic numbers to element symbols, +# +# this dict is used to back-convert atomic numbers to element symbols, # vdw_radii, and mass values elements_by_atomic_number = { - -1: { - "element_symbol": "X", - "name": "Unknown" - }, + -1: {"element_symbol": "X", "name": "Unknown"}, 1: { "element_symbol": "H", "name": "Hydrogen", "standard_mass": 1.00794, - "vdw_radii": 120 + "vdw_radii": 120, }, 2: { "element_symbol": "He", "name": "Helium", "standard_mass": 4.002602, - "vdw_radii": 140 + "vdw_radii": 140, }, 3: { "element_symbol": "Li", "name": "Lithium", "standard_mass": 6.941, - "vdw_radii": 182 + "vdw_radii": 182, }, 4: { "element_symbol": "Be", "name": "Beryllium", "standard_mass": 9.012182, - "vdw_radii": 153 + "vdw_radii": 153, }, 5: { "element_symbol": "B", "name": "Boron", "standard_mass": 10.811, - "vdw_radii": 192 + "vdw_radii": 192, }, 6: { "element_symbol": "C", "name": "Carbon", "standard_mass": 12.0107, - "vdw_radii": 170 + "vdw_radii": 170, }, 7: { "element_symbol": "N", "name": "Nitrogen", "standard_mass": 14.0067, - "vdw_radii": 155 + "vdw_radii": 155, }, 8: { "element_symbol": "O", "name": "Oxygen", "standard_mass": 15.9994, - "vdw_radii": 152 + "vdw_radii": 152, }, 9: { "element_symbol": "F", "name": "Fluorine", "standard_mass": 18.9984032, - "vdw_radii": 147 + "vdw_radii": 147, }, 10: { "element_symbol": "Ne", "name": "Neon", "standard_mass": 20.1797, - "vdw_radii": 154 + "vdw_radii": 154, }, 11: { "element_symbol": "Na", "name": "Sodium", "standard_mass": 22.98977, - "vdw_radii": 227 + "vdw_radii": 227, }, 12: { "element_symbol": "Mg", "name": "Magnesium", "standard_mass": 24.305, - "vdw_radii": 173 + "vdw_radii": 173, }, 13: { "element_symbol": "Al", "name": "Aluminium", "standard_mass": 26.981538, - "vdw_radii": 184 + "vdw_radii": 184, }, 14: { "element_symbol": "Si", "name": "Silicon", "standard_mass": 28.0855, - "vdw_radii": 210 + "vdw_radii": 210, }, 15: { "element_symbol": "P", "name": "Phosphorus", "standard_mass": 30.973761, - "vdw_radii": 180 + "vdw_radii": 180, }, 16: { "element_symbol": "S", "name": "Sulfur", "standard_mass": 32.065, - "vdw_radii": 180 + "vdw_radii": 180, }, 17: { "element_symbol": "Cl", "name": "Chlorine", "standard_mass": 35.453, - "vdw_radii": 175 + "vdw_radii": 175, }, 18: { "element_symbol": "Ar", "name": "Argon", "standard_mass": 39.948, - "vdw_radii": 188 + "vdw_radii": 188, }, 19: { "element_symbol": "K", "name": "Potassium", "standard_mass": 39.0983, - "vdw_radii": 275 + "vdw_radii": 275, }, 20: { "element_symbol": "Ca", "name": "Calcium", "standard_mass": 40.078, - "vdw_radii": 231 + "vdw_radii": 231, }, 21: { "element_symbol": "Sc", "name": "Scandium", "standard_mass": 44.95591, - "vdw_radii": 211 + "vdw_radii": 211, }, 22: { "element_symbol": "Ti", "name": "Titanium", "standard_mass": 47.867, - "vdw_radii": 147 + "vdw_radii": 147, }, 23: { "element_symbol": "V", "name": "Vanadium", "standard_mass": 50.9415, - "vdw_radii": 134 + "vdw_radii": 134, }, 24: { "element_symbol": "Cr", "name": "Chromium", "standard_mass": 51.9961, - "vdw_radii": 128 + "vdw_radii": 128, }, 25: { "element_symbol": "Mn", "name": "Manganese", "standard_mass": 54.938049, - "vdw_radii": 127 + "vdw_radii": 127, }, 26: { "element_symbol": "Fe", "name": "Iron", "standard_mass": 55.845, - "vdw_radii": 126 + "vdw_radii": 126, }, 27: { "element_symbol": "Co", "name": "Cobalt", "standard_mass": 58.9332, - "vdw_radii": 125 + "vdw_radii": 125, }, 28: { "element_symbol": "Ni", "name": "Nickel", "standard_mass": 58.6934, - "vdw_radii": 163 + "vdw_radii": 163, }, 29: { "element_symbol": "Cu", "name": "Copper", "standard_mass": 63.546, - "vdw_radii": 140 + "vdw_radii": 140, }, 30: { "element_symbol": "Zn", "name": "Zinc", "standard_mass": 65.409, - "vdw_radii": 139 + "vdw_radii": 139, }, 31: { "element_symbol": "Ga", "name": "Gallium", "standard_mass": 69.723, - "vdw_radii": 187 + "vdw_radii": 187, }, 32: { "element_symbol": "Ge", "name": "Germanium", "standard_mass": 72.64, - "vdw_radii": 211 + "vdw_radii": 211, }, 33: { "element_symbol": "As", "name": "Arsenic", "standard_mass": 74.9216, - "vdw_radii": 185 + "vdw_radii": 185, }, 34: { "element_symbol": "Se", "name": "Selenium", "standard_mass": 78.96, - "vdw_radii": 190 + "vdw_radii": 190, }, 35: { "element_symbol": "Br", "name": "Bromine", "standard_mass": 79.904, - "vdw_radii": 185 + "vdw_radii": 185, }, 36: { "element_symbol": "Kr", "name": "Krypton", "standard_mass": 83.798, - "vdw_radii": 202 + "vdw_radii": 202, }, 37: { "element_symbol": "Rb", "name": "Rubidium", "standard_mass": 85.4678, - "vdw_radii": 303 + "vdw_radii": 303, }, 38: { "element_symbol": "Sr", "name": "Strontium", "standard_mass": 87.62, - "vdw_radii": 249 + "vdw_radii": 249, }, 39: { "element_symbol": "Y", "name": "Yttrium", "standard_mass": 88.90585, - "vdw_radii": 180 + "vdw_radii": 180, }, 40: { "element_symbol": "Zr", "name": "Zirconium", "standard_mass": 91.224, - "vdw_radii": 160 + "vdw_radii": 160, }, 41: { "element_symbol": "Nb", "name": "Niobium", "standard_mass": 92.90638, - "vdw_radii": 146 + "vdw_radii": 146, }, 42: { "element_symbol": "Mo", "name": "Molybdenum", "standard_mass": 95.94, - "vdw_radii": 239 + "vdw_radii": 239, }, 43: { "element_symbol": "Tc", "name": "Technetium", "standard_mass": 98.9062, - "vdw_radii": 136 + "vdw_radii": 136, }, 44: { "element_symbol": "Ru", "name": "Ruthenium", "standard_mass": 101.07, - "vdw_radii": 134 + "vdw_radii": 134, }, 45: { "element_symbol": "Rh", "name": "Rhodium", "standard_mass": 102.9055, - "vdw_radii": 137 + "vdw_radii": 137, }, 46: { "element_symbol": "Pd", "name": "Palladium", "standard_mass": 106.42, - "vdw_radii": 144 - }, - 47: { - "element_symbol": "Ag", - "name": "Silver", - "standard_mass": 107.8682 - }, - 48: { - "element_symbol": "Cd", - "name": "Cadmium", - "standard_mass": 112.411 - }, - 49: { - "element_symbol": "In", - "name": "Indium", - "standard_mass": 114.818 - }, - 50: { - "element_symbol": "Sn", - "name": "Tin", - "standard_mass": 118.71 - }, - 51: { - "element_symbol": "Sb", - "name": "Antimony", - "standard_mass": 121.76 - }, - 52: { - "element_symbol": "Te", - "name": "Tellurium", - "standard_mass": 127.6 - }, - 53: { - "element_symbol": "I", - "name": "Iodine", - "standard_mass": 126.90447 - }, - 54: { - "element_symbol": "Xe", - "name": "Xenon", - "standard_mass": 131.293 - }, - 55: { - "element_symbol": "Cs", - "name": "Caesium", - "standard_mass": 132.90545 - }, - 56: { - "element_symbol": "Ba", - "name": "Barium", - "standard_mass": 137.327 - }, - 57: { - "element_symbol": "La", - "name": "Lanthanum", - "standard_mass": 138.9055 - }, - 58: { - "element_symbol": "Ce", - "name": "Cerium", - "standard_mass": 140.116 - }, - 59: { - "element_symbol": "Pr", - "name": "Praseodymium", - "standard_mass": 140.90765 - }, - 60: { - "element_symbol": "Nd", - "name": "Neodymium", - "standard_mass": 144.24 - }, - 61: { - "element_symbol": "Pm", - "name": "Promethium", - "standard_mass": 145 - }, - 62: { - "element_symbol": "Sm", - "name": "Samarium", - "standard_mass": 150.36 - }, - 63: { - "element_symbol": "Eu", - "name": "Europium", - "standard_mass": 151.964 - }, - 64: { - "element_symbol": "Gd", - "name": "Gadolinium", - "standard_mass": 157.25 - }, - 65: { - "element_symbol": "Tb", - "name": "Terbium", - "standard_mass": 158.92534 - }, - 66: { - "element_symbol": "Dy", - "name": "Dysprosium", - "standard_mass": 162.5 - }, - 67: { - "element_symbol": "Ho", - "name": "Holmium", - "standard_mass": 164.93032 - }, - 68: { - "element_symbol": "Er", - "name": "Erbium", - "standard_mass": 167.259 - }, - 69: { - "element_symbol": "Tm", - "name": "Thulium", - "standard_mass": 168.93421 - }, - 70: { - "element_symbol": "Yb", - "name": "Ytterbium", - "standard_mass": 173.04 - }, - 71: { - "element_symbol": "Lu", - "name": "Lutetium", - "standard_mass": 174.967 - }, - 72: { - "element_symbol": "Hf", - "name": "Hafnium", - "standard_mass": 178.49 - }, - 73: { - "element_symbol": "Ta", - "name": "Tantalum", - "standard_mass": 180.9479 - }, - 74: { - "element_symbol": "W", - "name": "Tungsten", - "standard_mass": 183.84 - }, - 75: { - "element_symbol": "Re", - "name": "Rhenium", - "standard_mass": 186.207 - }, - 76: { - "element_symbol": "Os", - "name": "Osmium", - "standard_mass": 190.23 - }, - 77: { - "element_symbol": "Ir", - "name": "Iridium", - "standard_mass": 192.217 - }, - 78: { - "element_symbol": "Pt", - "name": "Platinum", - "standard_mass": 195.078 - }, - 79: { - "element_symbol": "Au", - "name": "Gold", - "standard_mass": 196.96655 - }, - 80: { - "element_symbol": "Hg", - "name": "Mercury", - "standard_mass": 200.59 - }, - 81: { - "element_symbol": "Tl", - "name": "Thallium", - "standard_mass": 204.3833 - }, - 82: { - "element_symbol": "Pb", - "name": "Lead", - "standard_mass": 207.2 - }, - 83: { - "element_symbol": "Bi", - "name": "Bismuth", - "standard_mass": 208.98038 - }, - 84: { - "element_symbol": "Po", - "name": "Polonium", - "standard_mass": 209 - }, - 85: { - "element_symbol": "At", - "name": "Astatine", - "standard_mass": 210 - }, - 86: { - "element_symbol": "Rn", - "name": "Radon", - "standard_mass": 222 - }, - 87: { - "element_symbol": "Fr", - "name": "Francium", - "standard_mass": 223 - }, - 88: { - "element_symbol": "Ra", - "name": "Radium", - "standard_mass": 226.0254 - }, - 89: { - "element_symbol": "Ac", - "name": "Actinium", - "standard_mass": 227 - }, - 90: { - "element_symbol": "Th", - "name": "Thorium", - "standard_mass": 232.0381 - }, - 91: { - "element_symbol": "Pa", - "name": "Protactinium", - "standard_mass": 231.03588 - }, - 92: { - "element_symbol": "U", - "name": "Uranium", - "standard_mass": 238.02891 - }, - 93: { - "element_symbol": "Np", - "name": "Neptunium", - "standard_mass": 237.0482 - }, - 94: { - "element_symbol": "Pu", - "name": "Plutonium", - "standard_mass": 244 - }, - 95: { - "element_symbol": "Am", - "name": "Americium", - "standard_mass": 243 - }, - 96: { - "element_symbol": "Cm", - "name": "Curium", - "standard_mass": 243 - }, - 97: { - "element_symbol": "Bk", - "name": "Berkelium", - "standard_mass": 247 - }, - 98: { - "element_symbol": "Cf", - "name": "Californium", - "standard_mass": 251 - }, - 99: { - "element_symbol": "Es", - "name": "Einsteinium", - "standard_mass": 254 - }, - 100: { - "element_symbol": "Fm", - "name": "Fermium", - "standard_mass": 254 - }, - 101: { - "element_symbol": "Md", - "name": "Mendelevium", - "standard_mass": 258 - }, - 102: { - "element_symbol": "No", - "name": "Nobelium", - "standard_mass": 259 - }, - 103: { - "element_symbol": "Lr", - "name": "Lawrencium", - "standard_mass": 262 - } + "vdw_radii": 144, + }, + 47: {"element_symbol": "Ag", "name": "Silver", "standard_mass": 107.8682}, + 48: {"element_symbol": "Cd", "name": "Cadmium", "standard_mass": 112.411}, + 49: {"element_symbol": "In", "name": "Indium", "standard_mass": 114.818}, + 50: {"element_symbol": "Sn", "name": "Tin", "standard_mass": 118.71}, + 51: {"element_symbol": "Sb", "name": "Antimony", "standard_mass": 121.76}, + 52: {"element_symbol": "Te", "name": "Tellurium", "standard_mass": 127.6}, + 53: {"element_symbol": "I", "name": "Iodine", "standard_mass": 126.90447}, + 54: {"element_symbol": "Xe", "name": "Xenon", "standard_mass": 131.293}, + 55: {"element_symbol": "Cs", "name": "Caesium", "standard_mass": 132.90545}, + 56: {"element_symbol": "Ba", "name": "Barium", "standard_mass": 137.327}, + 57: {"element_symbol": "La", "name": "Lanthanum", "standard_mass": 138.9055}, + 58: {"element_symbol": "Ce", "name": "Cerium", "standard_mass": 140.116}, + 59: {"element_symbol": "Pr", "name": "Praseodymium", "standard_mass": 140.90765}, + 60: {"element_symbol": "Nd", "name": "Neodymium", "standard_mass": 144.24}, + 61: {"element_symbol": "Pm", "name": "Promethium", "standard_mass": 145}, + 62: {"element_symbol": "Sm", "name": "Samarium", "standard_mass": 150.36}, + 63: {"element_symbol": "Eu", "name": "Europium", "standard_mass": 151.964}, + 64: {"element_symbol": "Gd", "name": "Gadolinium", "standard_mass": 157.25}, + 65: {"element_symbol": "Tb", "name": "Terbium", "standard_mass": 158.92534}, + 66: {"element_symbol": "Dy", "name": "Dysprosium", "standard_mass": 162.5}, + 67: {"element_symbol": "Ho", "name": "Holmium", "standard_mass": 164.93032}, + 68: {"element_symbol": "Er", "name": "Erbium", "standard_mass": 167.259}, + 69: {"element_symbol": "Tm", "name": "Thulium", "standard_mass": 168.93421}, + 70: {"element_symbol": "Yb", "name": "Ytterbium", "standard_mass": 173.04}, + 71: {"element_symbol": "Lu", "name": "Lutetium", "standard_mass": 174.967}, + 72: {"element_symbol": "Hf", "name": "Hafnium", "standard_mass": 178.49}, + 73: {"element_symbol": "Ta", "name": "Tantalum", "standard_mass": 180.9479}, + 74: {"element_symbol": "W", "name": "Tungsten", "standard_mass": 183.84}, + 75: {"element_symbol": "Re", "name": "Rhenium", "standard_mass": 186.207}, + 76: {"element_symbol": "Os", "name": "Osmium", "standard_mass": 190.23}, + 77: {"element_symbol": "Ir", "name": "Iridium", "standard_mass": 192.217}, + 78: {"element_symbol": "Pt", "name": "Platinum", "standard_mass": 195.078}, + 79: {"element_symbol": "Au", "name": "Gold", "standard_mass": 196.96655}, + 80: {"element_symbol": "Hg", "name": "Mercury", "standard_mass": 200.59}, + 81: {"element_symbol": "Tl", "name": "Thallium", "standard_mass": 204.3833}, + 82: {"element_symbol": "Pb", "name": "Lead", "standard_mass": 207.2}, + 83: {"element_symbol": "Bi", "name": "Bismuth", "standard_mass": 208.98038}, + 84: {"element_symbol": "Po", "name": "Polonium", "standard_mass": 209}, + 85: {"element_symbol": "At", "name": "Astatine", "standard_mass": 210}, + 86: {"element_symbol": "Rn", "name": "Radon", "standard_mass": 222}, + 87: {"element_symbol": "Fr", "name": "Francium", "standard_mass": 223}, + 88: {"element_symbol": "Ra", "name": "Radium", "standard_mass": 226.0254}, + 89: {"element_symbol": "Ac", "name": "Actinium", "standard_mass": 227}, + 90: {"element_symbol": "Th", "name": "Thorium", "standard_mass": 232.0381}, + 91: {"element_symbol": "Pa", "name": "Protactinium", "standard_mass": 231.03588}, + 92: {"element_symbol": "U", "name": "Uranium", "standard_mass": 238.02891}, + 93: {"element_symbol": "Np", "name": "Neptunium", "standard_mass": 237.0482}, + 94: {"element_symbol": "Pu", "name": "Plutonium", "standard_mass": 244}, + 95: {"element_symbol": "Am", "name": "Americium", "standard_mass": 243}, + 96: {"element_symbol": "Cm", "name": "Curium", "standard_mass": 243}, + 97: {"element_symbol": "Bk", "name": "Berkelium", "standard_mass": 247}, + 98: {"element_symbol": "Cf", "name": "Californium", "standard_mass": 251}, + 99: {"element_symbol": "Es", "name": "Einsteinium", "standard_mass": 254}, + 100: {"element_symbol": "Fm", "name": "Fermium", "standard_mass": 254}, + 101: {"element_symbol": "Md", "name": "Mendelevium", "standard_mass": 258}, + 102: {"element_symbol": "No", "name": "Nobelium", "standard_mass": 259}, + 103: {"element_symbol": "Lr", "name": "Lawrencium", "standard_mass": 262}, } # coarse_grain_particles dictionary is currently being used as a backup for # non-standard atom names that were mixed in with the elements dictionary coarse_grain_particles = { - "BB": {"atomic_number": 6, 'vdw_radii': 250, "name": "MartiniBB"}, - "CD": {"atomic_number": 6, 'vdw_radii': 170, "name": "MartiniLipidCarbonD"}, - "D" : {"atomic_number": 6, 'vdw_radii': 170, "name": "Carbon"}, - "GL": {"atomic_number": 8, 'vdw_radii': 180, "name": "MartiniGL"}, - "SC": {"atomic_number": 16, 'vdw_radii': 200, "name": "MartiniSC"}#, + "BB": {"atomic_number": 6, "vdw_radii": 250, "name": "MartiniBB"}, + "CD": {"atomic_number": 6, "vdw_radii": 170, "name": "MartiniLipidCarbonD"}, + "D": {"atomic_number": 6, "vdw_radii": 170, "name": "Carbon"}, + "GL": {"atomic_number": 8, "vdw_radii": 180, "name": "MartiniGL"}, + "SC": {"atomic_number": 16, "vdw_radii": 200, "name": "MartiniSC"}, # , ## just kept since these were entries in the old `elements` dictionary - #"NA": {"atomic_number": 11, 'vdw_radii': 227, "name": "Sodium"}, - #"CL": {"atomic_number": 17, 'vdw_radii': 175, "name": "Chlorine"} - } + # "NA": {"atomic_number": 11, 'vdw_radii': 227, "name": "Sodium"}, + # "CL": {"atomic_number": 17, 'vdw_radii': 175, "name": "Chlorine"} +} residues = { # unknown? Came up in one of the structures, haven't looked into it yet # TODO look into it! - "UNK": {"res_name_num": -1, "res_type": "unknown", "res_type_no": 1}, - + "UNK": {"res_name_num": -1, "res_type": "unknown", "res_type_no": 1}, # TODO implement an attribute that uses the residue types (basic / polar / acid etc) # 20 naturally occurring amino acids - "ALA": {"res_name_num": 0, "res_type": "apolar", "res_type_no": 1}, - "ARG": {"res_name_num": 1, "res_type": "basic", "res_type_no": 1}, - "ASN": {"res_name_num": 2, "res_type": "polar", "res_type_no": 1}, - "ASP": {"res_name_num": 3, "res_type": "acid", "res_type_no": 1}, + "ALA": {"res_name_num": 0, "res_type": "apolar", "res_type_no": 1}, + "ARG": {"res_name_num": 1, "res_type": "basic", "res_type_no": 1}, + "ASN": {"res_name_num": 2, "res_type": "polar", "res_type_no": 1}, + "ASP": {"res_name_num": 3, "res_type": "acid", "res_type_no": 1}, # can act as acid, but mostly polar? - "CYS": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, - "GLU": {"res_name_num": 5, "res_type": "acid", "res_type_no": 1}, - "GLN": {"res_name_num": 6, "res_type": "polar", "res_type_no": 1}, - "GLY": {"res_name_num": 7, "res_type": "apolar", "res_type_no": 1}, + "CYS": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, + "GLU": {"res_name_num": 5, "res_type": "acid", "res_type_no": 1}, + "GLN": {"res_name_num": 6, "res_type": "polar", "res_type_no": 1}, + "GLY": {"res_name_num": 7, "res_type": "apolar", "res_type_no": 1}, # ambiguous - "HIS": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, - "ILE": {"res_name_num": 9, "res_type": "apolar", "res_type_no": 1}, - "LEU": {"res_name_num": 10, "res_type": "apolar", "res_type_no": 1}, - "LYS": {"res_name_num": 11, "res_type": "basic", "res_type_no": 1}, - "MET": {"res_name_num": 12, "res_type": "apolar", "res_type_no": 1}, + "HIS": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, + "ILE": {"res_name_num": 9, "res_type": "apolar", "res_type_no": 1}, + "LEU": {"res_name_num": 10, "res_type": "apolar", "res_type_no": 1}, + "LYS": {"res_name_num": 11, "res_type": "basic", "res_type_no": 1}, + "MET": {"res_name_num": 12, "res_type": "apolar", "res_type_no": 1}, "PHE": {"res_name_num": 13, "res_type": "aromatic", "res_type_no": 1}, - "PRO": {"res_name_num": 14, "res_type": "apolar", "res_type_no": 1}, - "SER": {"res_name_num": 15, "res_type": "polar", "res_type_no": 1}, - "THR": {"res_name_num": 16, "res_type": "polar", "res_type_no": 1}, + "PRO": {"res_name_num": 14, "res_type": "apolar", "res_type_no": 1}, + "SER": {"res_name_num": 15, "res_type": "polar", "res_type_no": 1}, + "THR": {"res_name_num": 16, "res_type": "polar", "res_type_no": 1}, "TRP": {"res_name_num": 17, "res_type": "aromatic", "res_type_no": 1}, "TYR": {"res_name_num": 18, "res_type": "aromatic", "res_type_no": 1}, - "VAL": {"res_name_num": 19, "res_type": "apolar", "res_type_no": 1}, - + "VAL": {"res_name_num": 19, "res_type": "apolar", "res_type_no": 1}, # modified amino acids, unsure how to deal with but currently will label as the # same res_name number # S-nitrosylation of cysteine "SNC": {"res_name_num": 15, "res_type": "unkown", "res_type_no": 1}, # selenomethionine "MSE": {"res_name_num": 12, "res_type": "unkown", "res_type_no": 1}, - # add conventional AMBER FF residue names with different protonations - "ASH": {"res_name_num": 3, "res_type": "acid", "res_type_no": 1}, - "CYM": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, - "CYX": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, - "GLH": {"res_name_num": 5, "res_type": "acid", "res_type_no": 1}, - "HID": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, - "HIE": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, - "HIP": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, - "HYP": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, - "LYN": {"res_name_num": 11, "res_type": "basic", "res_type_no": 1}, - + "ASH": {"res_name_num": 3, "res_type": "acid", "res_type_no": 1}, + "CYM": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, + "CYX": {"res_name_num": 4, "res_type": "polar", "res_type_no": 1}, + "GLH": {"res_name_num": 5, "res_type": "acid", "res_type_no": 1}, + "HID": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, + "HIE": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, + "HIP": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, + "HYP": {"res_name_num": 8, "res_type": "polar", "res_type_no": 1}, + "LYN": {"res_name_num": 11, "res_type": "basic", "res_type_no": 1}, # nucleic acids # DNA - "DA": {"res_name_num": 30, "res_type": "unkown", "res_type_no": 1}, - "DC": {"res_name_num": 31, "res_type": "unkown", "res_type_no": 1}, - "DG": {"res_name_num": 32, "res_type": "unkown", "res_type_no": 1}, - "DT": {"res_name_num": 33, "res_type": "unkown", "res_type_no": 1}, - - "PST": {"res_name_num": 33, "res_type": "unkown", "res_type_no": 1}, + "DA": {"res_name_num": 30, "res_type": "unkown", "res_type_no": 1}, + "DC": {"res_name_num": 31, "res_type": "unkown", "res_type_no": 1}, + "DG": {"res_name_num": 32, "res_type": "unkown", "res_type_no": 1}, + "DT": {"res_name_num": 33, "res_type": "unkown", "res_type_no": 1}, + "PST": {"res_name_num": 33, "res_type": "unkown", "res_type_no": 1}, # RNA - "A": {"res_name_num": 40, "res_type": "unkown", "res_type_no": 1}, - "C": {"res_name_num": 41, "res_type": "unkown", "res_type_no": 1}, - "G": {"res_name_num": 42, "res_type": "unkown", "res_type_no": 1}, - "U": {"res_name_num": 43, "res_type": "unkown", "res_type_no": 1} - + "A": {"res_name_num": 40, "res_type": "unkown", "res_type_no": 1}, + "C": {"res_name_num": 41, "res_type": "unkown", "res_type_no": 1}, + "G": {"res_name_num": 42, "res_type": "unkown", "res_type_no": 1}, + "U": {"res_name_num": 43, "res_type": "unkown", "res_type_no": 1}, # need to add some sugars and such here as well } @@ -1251,59 +782,53 @@ # rotation point around the alpha carbon is: 2 atom_names = { # backbone atoms - 'N': 1, - 'CA': 2, # rotation point - 'C': 3, - 'O': 4, - + "N": 1, + "CA": 2, # rotation point + "C": 3, + "O": 4, # sidechain atoms - 'CB': 5, # rotation point - 'CG': 6, # rotation point - 'CG1': 7, - 'CG2': 8, - 'OG': 9, - 'OG1': 10, - 'SG': 11, - 'CD': 12, # rotation point - 'CD1': 13, - 'CD2': 14, - 'ND1': 15, - 'ND2': 16, - 'OD1': 17, - 'OD2': 18, - 'SD': 19, - 'CE': 20, # rotation point - 'CE1': 21, - 'CE2': 23, - 'CE3': 24, - 'NE': 25, # rotation point - 'NE1': 26, - 'NE2': 27, - 'OE1': 28, - 'OE2': 29, - 'CH2': 30, - 'NH1': 31, - 'NH2': 32, - 'OH': 33, - 'CZ': 34, - 'CZ2': 35, - 'CZ3': 36, - 'NZ': 37, + "CB": 5, # rotation point + "CG": 6, # rotation point + "CG1": 7, + "CG2": 8, + "OG": 9, + "OG1": 10, + "SG": 11, + "CD": 12, # rotation point + "CD1": 13, + "CD2": 14, + "ND1": 15, + "ND2": 16, + "OD1": 17, + "OD2": 18, + "SD": 19, + "CE": 20, # rotation point + "CE1": 21, + "CE2": 23, + "CE3": 24, + "NE": 25, # rotation point + "NE1": 26, + "NE2": 27, + "OE1": 28, + "OE2": 29, + "CH2": 30, + "NH1": 31, + "NH2": 32, + "OH": 33, + "CZ": 34, + "CZ2": 35, + "CZ3": 36, + "NZ": 37, # terminus of a peptide chain when it ends in an oxygen, maybe move higher? (in the 'backbone') - 'OXT': 38, - - + "OXT": 38, # DNA # currently works for RNA as well, but haven't optimised - # phosphate "P": 50, # connection to the previous ribose - # These two atoms can sometimes have their names written different ways, # this covers both to ensure their names are assigned properly. "O1P": 51, "OP1": 51, - "OP2": 52, "O2P": 52, # ribose @@ -1316,12 +841,10 @@ "C2'": 59, # ring "O2'": 60, "C1'": 61, # ring # connection to the base - # connection point to the base "N1": 62, "N9": 63, "N3": 64, - "C8": 65, "N7": 66, "C5": 67, @@ -1333,14 +856,10 @@ "C4": 71, "O6": 72, "N2": 73, - "N4": 74, "O2": 75, - "O4": 76, - "C7": 77 # another extra carbon? - - + "C7": 77, # another extra carbon? # unsure how to proceed past this point, as atom names are reused inside of # different bases but represent totally different locations like the N9 and N1 atoms # can both be the connecting carbon to the ribose, or a carbon much further into the @@ -1351,116 +870,2277 @@ # atom charges taken from AMBER force field source code atom_charge = { # peptide charges - 'ALA': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0337, 'HA': 0.0823, 'CB': -0.1825, 'HB1': 0.0603, 'HB2': 0.0603, 'HB3': 0.0603, 'C': 0.5973, 'O': -0.5679}, - 'ARG': {'N': -0.3479, 'H': 0.2747, 'CA': -0.2637, 'HA': 0.156, 'CB': -0.0007, 'HB2': 0.0327, 'HB3': 0.0327, 'CG': 0.039, 'HG2': 0.0285, 'HG3': 0.0285, 'CD': 0.0486, 'HD2': 0.0687, 'HD3': 0.0687, 'NE': -0.5295, 'HE': 0.3456, 'CZ': 0.8076, 'NH1': -0.8627, 'HH11': 0.4478, 'HH12': 0.4478, 'NH2': -0.8627, 'HH21': 0.4478, 'HH22': 0.4478, 'C': 0.7341, 'O': -0.5894}, - 'ASH': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0341, 'HA': 0.0864, 'CB': -0.0316, 'HB2': 0.0488, 'HB3': 0.0488, 'CG': 0.6462, 'OD1': -0.5554, 'OD2': -0.6376, 'HD2': 0.4747, 'C': 0.5973, 'O': -0.5679}, - 'ASN': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0143, 'HA': 0.1048, 'CB': -0.2041, 'HB2': 0.0797, 'HB3': 0.0797, 'CG': 0.713, 'OD1': -0.5931, 'ND2': -0.9191, 'HD21': 0.4196, 'HD22': 0.4196, 'C': 0.5973, 'O': -0.5679}, - 'ASP': {'N': -0.5163, 'H': 0.2936, 'CA': 0.0381, 'HA': 0.088, 'CB': -0.0303, 'HB2': -0.0122, 'HB3': -0.0122, 'CG': 0.7994, 'OD1': -0.8014, 'OD2': -0.8014, 'C': 0.5366, 'O': -0.5819}, - 'CYM': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0351, 'HA': 0.0508, 'CB': -0.2413, 'HB3': 0.1122, 'HB2': 0.1122, 'SG': -0.8844, 'C': 0.5973, 'O': -0.5679}, - 'CYS': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0213, 'HA': 0.1124, 'CB': -0.1231, 'HB2': 0.1112, 'HB3': 0.1112, 'SG': -0.3119, 'HG': 0.1933, 'C': 0.5973, 'O': -0.5679}, - 'CYX': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0429, 'HA': 0.0766, 'CB': -0.079, 'HB2': 0.091, 'HB3': 0.091, 'SG': -0.1081, 'C': 0.5973, 'O': -0.5679}, - 'GLH': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0145, 'HA': 0.0779, 'CB': -0.0071, 'HB2': 0.0256, 'HB3': 0.0256, 'CG': -0.0174, 'HG2': 0.043, 'HG3': 0.043, 'CD': 0.6801, 'OE1': -0.5838, 'OE2': -0.6511, 'HE2': 0.4641, 'C': 0.5973, 'O': -0.5679}, - 'GLN': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0031, 'HA': 0.085, 'CB': -0.0036, 'HB2': 0.0171, 'HB3': 0.0171, 'CG': -0.0645, 'HG2': 0.0352, 'HG3': 0.0352, 'CD': 0.6951, 'OE1': -0.6086, 'NE2': -0.9407, 'HE21': 0.4251, 'HE22': 0.4251, 'C': 0.5973, 'O': -0.5679}, - 'GLU': {'N': -0.5163, 'H': 0.2936, 'CA': 0.0397, 'HA': 0.1105, 'CB': 0.056, 'HB2': -0.0173, 'HB3': -0.0173, 'CG': 0.0136, 'HG2': -0.0425, 'HG3': -0.0425, 'CD': 0.8054, 'OE1': -0.8188, 'OE2': -0.8188, 'C': 0.5366, 'O': -0.5819}, - 'GLY': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0252, 'HA2': 0.0698, 'HA3': 0.0698, 'C': 0.5973, 'O': -0.5679}, - 'HID': {'N': -0.4157, 'H': 0.2719, 'CA': 0.0188, 'HA': 0.0881, 'CB': -0.0462, 'HB2': 0.0402, 'HB3': 0.0402, 'CG': -0.0266, 'ND1': -0.3811, 'HD1': 0.3649, 'CE1': 0.2057, 'HE1': 0.1392, 'NE2': -0.5727, 'CD2': 0.1292, 'HD2': 0.1147, 'C': 0.5973, 'O': -0.5679}, - 'HIE': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0581, 'HA': 0.136, 'CB': -0.0074, 'HB2': 0.0367, 'HB3': 0.0367, 'CG': 0.1868, 'ND1': -0.5432, 'CE1': 0.1635, 'HE1': 0.1435, 'NE2': -0.2795, 'HE2': 0.3339, 'CD2': -0.2207, 'HD2': 0.1862, 'C': 0.5973, 'O': -0.5679}, - 'HIP': {'N': -0.3479, 'H': 0.2747, 'CA': -0.1354, 'HA': 0.1212, 'CB': -0.0414, 'HB2': 0.081, 'HB3': 0.081, 'CG': -0.0012, 'ND1': -0.1513, 'HD1': 0.3866, 'CE1': -0.017, 'HE1': 0.2681, 'NE2': -0.1718, 'HE2': 0.3911, 'CD2': -0.1141, 'HD2': 0.2317, 'C': 0.7341, 'O': -0.5894}, - 'ILE': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0597, 'HA': 0.0869, 'CB': 0.1303, 'HB': 0.0187, 'CG2': -0.3204, 'HG21': 0.0882, 'HG22': 0.0882, 'HG23': 0.0882, 'CG1': -0.043, 'HG12': 0.0236, 'HG13': 0.0236, 'CD1': -0.066, 'HD11': 0.0186, 'HD12': 0.0186, 'HD13': 0.0186, 'C': 0.5973, 'O': -0.5679}, - 'LEU': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0518, 'HA': 0.0922, 'CB': -0.1102, 'HB2': 0.0457, 'HB3': 0.0457, 'CG': 0.3531, 'HG': -0.0361, 'CD1': -0.4121, 'HD11': 0.1, 'HD12': 0.1, 'HD13': 0.1, 'CD2': -0.4121, 'HD21': 0.1, 'HD22': 0.1, 'HD23': 0.1, 'C': 0.5973, 'O': -0.5679}, - 'LYN': {'N': -0.4157, 'H': 0.2719, 'CA': -0.07206, 'HA': 0.0994, 'CB': -0.04845, 'HB2': 0.034, 'HB3': 0.034, 'CG': 0.06612, 'HG2': 0.01041, 'HG3': 0.01041, 'CD': -0.03768, 'HD2': 0.01155, 'HD3': 0.01155, 'CE': 0.32604, 'HE2': -0.03358, 'HE3': -0.03358, 'NZ': -1.03581, 'HZ2': 0.38604, 'HZ3': 0.38604, 'C': 0.5973, 'O': -0.5679}, - 'LYS': {'N': -0.3479, 'H': 0.2747, 'CA': -0.24, 'HA': 0.1426, 'CB': -0.0094, 'HB2': 0.0362, 'HB3': 0.0362, 'CG': 0.0187, 'HG2': 0.0103, 'HG3': 0.0103, 'CD': -0.0479, 'HD2': 0.0621, 'HD3': 0.0621, 'CE': -0.0143, 'HE2': 0.1135, 'HE3': 0.1135, 'NZ': -0.3854, 'HZ1': 0.34, 'HZ2': 0.34, 'HZ3': 0.34, 'C': 0.7341, 'O': -0.5894}, - 'MET': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0237, 'HA': 0.088, 'CB': 0.0342, 'HB2': 0.0241, 'HB3': 0.0241, 'CG': 0.0018, 'HG2': 0.044, 'HG3': 0.044, 'SD': -0.2737, 'CE': -0.0536, 'HE1': 0.0684, 'HE2': 0.0684, 'HE3': 0.0684, 'C': 0.5973, 'O': -0.5679}, - 'PHE': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0024, 'HA': 0.0978, 'CB': -0.0343, 'HB2': 0.0295, 'HB3': 0.0295, 'CG': 0.0118, 'CD1': -0.1256, 'HD1': 0.133, 'CE1': -0.1704, 'HE1': 0.143, 'CZ': -0.1072, 'HZ': 0.1297, 'CE2': -0.1704, 'HE2': 0.143, 'CD2': -0.1256, 'HD2': 0.133, 'C': 0.5973, 'O': -0.5679}, - 'PRO': {'N': -0.2548, 'CD': 0.0192, 'HD2': 0.0391, 'HD3': 0.0391, 'CG': 0.0189, 'HG2': 0.0213, 'HG3': 0.0213, 'CB': -0.007, 'HB2': 0.0253, 'HB3': 0.0253, 'CA': -0.0266, 'HA': 0.0641, 'C': 0.5896, 'O': -0.5748}, - 'SER': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0249, 'HA': 0.0843, 'CB': 0.2117, 'HB2': 0.0352, 'HB3': 0.0352, 'OG': -0.6546, 'HG': 0.4275, 'C': 0.5973, 'O': -0.5679}, - 'THR': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0389, 'HA': 0.1007, 'CB': 0.3654, 'HB': 0.0043, 'CG2': -0.2438, 'HG21': 0.0642, 'HG22': 0.0642, 'HG23': 0.0642, 'OG1': -0.6761, 'HG1': 0.4102, 'C': 0.5973, 'O': -0.5679}, - 'TRP': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0275, 'HA': 0.1123, 'CB': -0.005, 'HB2': 0.0339, 'HB3': 0.0339, 'CG': -0.1415, 'CD1': -0.1638, 'HD1': 0.2062, 'NE1': -0.3418, 'HE1': 0.3412, 'CE2': 0.138, 'CZ2': -0.2601, 'HZ2': 0.1572, 'CH2': -0.1134, 'HH2': 0.1417, 'CZ3': -0.1972, 'HZ3': 0.1447, 'CE3': -0.2387, 'HE3': 0.17, 'CD2': 0.1243, 'C': 0.5973, 'O': -0.5679}, - 'TYR': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0014, 'HA': 0.0876, 'CB': -0.0152, 'HB2': 0.0295, 'HB3': 0.0295, 'CG': -0.0011, 'CD1': -0.1906, 'HD1': 0.1699, 'CE1': -0.2341, 'HE1': 0.1656, 'CZ': 0.3226, 'OH': -0.5579, 'HH': 0.3992, 'CE2': -0.2341, 'HE2': 0.1656, 'CD2': -0.1906, 'HD2': 0.1699, 'C': 0.5973, 'O': -0.5679}, - 'VAL': {'N': -0.4157, 'H': 0.2719, 'CA': -0.0875, 'HA': 0.0969, 'CB': 0.2985, 'HB': -0.0297, 'CG1': -0.3192, 'HG11': 0.0791, 'HG12': 0.0791, 'HG13': 0.0791, 'CG2': -0.3192, 'HG21': 0.0791, 'HG22': 0.0791, 'HG23': 0.0791, 'C': 0.5973, 'O': -0.5679}, - + "ALA": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0337, + "HA": 0.0823, + "CB": -0.1825, + "HB1": 0.0603, + "HB2": 0.0603, + "HB3": 0.0603, + "C": 0.5973, + "O": -0.5679, + }, + "ARG": { + "N": -0.3479, + "H": 0.2747, + "CA": -0.2637, + "HA": 0.156, + "CB": -0.0007, + "HB2": 0.0327, + "HB3": 0.0327, + "CG": 0.039, + "HG2": 0.0285, + "HG3": 0.0285, + "CD": 0.0486, + "HD2": 0.0687, + "HD3": 0.0687, + "NE": -0.5295, + "HE": 0.3456, + "CZ": 0.8076, + "NH1": -0.8627, + "HH11": 0.4478, + "HH12": 0.4478, + "NH2": -0.8627, + "HH21": 0.4478, + "HH22": 0.4478, + "C": 0.7341, + "O": -0.5894, + }, + "ASH": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0341, + "HA": 0.0864, + "CB": -0.0316, + "HB2": 0.0488, + "HB3": 0.0488, + "CG": 0.6462, + "OD1": -0.5554, + "OD2": -0.6376, + "HD2": 0.4747, + "C": 0.5973, + "O": -0.5679, + }, + "ASN": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0143, + "HA": 0.1048, + "CB": -0.2041, + "HB2": 0.0797, + "HB3": 0.0797, + "CG": 0.713, + "OD1": -0.5931, + "ND2": -0.9191, + "HD21": 0.4196, + "HD22": 0.4196, + "C": 0.5973, + "O": -0.5679, + }, + "ASP": { + "N": -0.5163, + "H": 0.2936, + "CA": 0.0381, + "HA": 0.088, + "CB": -0.0303, + "HB2": -0.0122, + "HB3": -0.0122, + "CG": 0.7994, + "OD1": -0.8014, + "OD2": -0.8014, + "C": 0.5366, + "O": -0.5819, + }, + "CYM": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0351, + "HA": 0.0508, + "CB": -0.2413, + "HB3": 0.1122, + "HB2": 0.1122, + "SG": -0.8844, + "C": 0.5973, + "O": -0.5679, + }, + "CYS": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0213, + "HA": 0.1124, + "CB": -0.1231, + "HB2": 0.1112, + "HB3": 0.1112, + "SG": -0.3119, + "HG": 0.1933, + "C": 0.5973, + "O": -0.5679, + }, + "CYX": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0429, + "HA": 0.0766, + "CB": -0.079, + "HB2": 0.091, + "HB3": 0.091, + "SG": -0.1081, + "C": 0.5973, + "O": -0.5679, + }, + "GLH": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0145, + "HA": 0.0779, + "CB": -0.0071, + "HB2": 0.0256, + "HB3": 0.0256, + "CG": -0.0174, + "HG2": 0.043, + "HG3": 0.043, + "CD": 0.6801, + "OE1": -0.5838, + "OE2": -0.6511, + "HE2": 0.4641, + "C": 0.5973, + "O": -0.5679, + }, + "GLN": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0031, + "HA": 0.085, + "CB": -0.0036, + "HB2": 0.0171, + "HB3": 0.0171, + "CG": -0.0645, + "HG2": 0.0352, + "HG3": 0.0352, + "CD": 0.6951, + "OE1": -0.6086, + "NE2": -0.9407, + "HE21": 0.4251, + "HE22": 0.4251, + "C": 0.5973, + "O": -0.5679, + }, + "GLU": { + "N": -0.5163, + "H": 0.2936, + "CA": 0.0397, + "HA": 0.1105, + "CB": 0.056, + "HB2": -0.0173, + "HB3": -0.0173, + "CG": 0.0136, + "HG2": -0.0425, + "HG3": -0.0425, + "CD": 0.8054, + "OE1": -0.8188, + "OE2": -0.8188, + "C": 0.5366, + "O": -0.5819, + }, + "GLY": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0252, + "HA2": 0.0698, + "HA3": 0.0698, + "C": 0.5973, + "O": -0.5679, + }, + "HID": { + "N": -0.4157, + "H": 0.2719, + "CA": 0.0188, + "HA": 0.0881, + "CB": -0.0462, + "HB2": 0.0402, + "HB3": 0.0402, + "CG": -0.0266, + "ND1": -0.3811, + "HD1": 0.3649, + "CE1": 0.2057, + "HE1": 0.1392, + "NE2": -0.5727, + "CD2": 0.1292, + "HD2": 0.1147, + "C": 0.5973, + "O": -0.5679, + }, + "HIE": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0581, + "HA": 0.136, + "CB": -0.0074, + "HB2": 0.0367, + "HB3": 0.0367, + "CG": 0.1868, + "ND1": -0.5432, + "CE1": 0.1635, + "HE1": 0.1435, + "NE2": -0.2795, + "HE2": 0.3339, + "CD2": -0.2207, + "HD2": 0.1862, + "C": 0.5973, + "O": -0.5679, + }, + "HIP": { + "N": -0.3479, + "H": 0.2747, + "CA": -0.1354, + "HA": 0.1212, + "CB": -0.0414, + "HB2": 0.081, + "HB3": 0.081, + "CG": -0.0012, + "ND1": -0.1513, + "HD1": 0.3866, + "CE1": -0.017, + "HE1": 0.2681, + "NE2": -0.1718, + "HE2": 0.3911, + "CD2": -0.1141, + "HD2": 0.2317, + "C": 0.7341, + "O": -0.5894, + }, + "ILE": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0597, + "HA": 0.0869, + "CB": 0.1303, + "HB": 0.0187, + "CG2": -0.3204, + "HG21": 0.0882, + "HG22": 0.0882, + "HG23": 0.0882, + "CG1": -0.043, + "HG12": 0.0236, + "HG13": 0.0236, + "CD1": -0.066, + "HD11": 0.0186, + "HD12": 0.0186, + "HD13": 0.0186, + "C": 0.5973, + "O": -0.5679, + }, + "LEU": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0518, + "HA": 0.0922, + "CB": -0.1102, + "HB2": 0.0457, + "HB3": 0.0457, + "CG": 0.3531, + "HG": -0.0361, + "CD1": -0.4121, + "HD11": 0.1, + "HD12": 0.1, + "HD13": 0.1, + "CD2": -0.4121, + "HD21": 0.1, + "HD22": 0.1, + "HD23": 0.1, + "C": 0.5973, + "O": -0.5679, + }, + "LYN": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.07206, + "HA": 0.0994, + "CB": -0.04845, + "HB2": 0.034, + "HB3": 0.034, + "CG": 0.06612, + "HG2": 0.01041, + "HG3": 0.01041, + "CD": -0.03768, + "HD2": 0.01155, + "HD3": 0.01155, + "CE": 0.32604, + "HE2": -0.03358, + "HE3": -0.03358, + "NZ": -1.03581, + "HZ2": 0.38604, + "HZ3": 0.38604, + "C": 0.5973, + "O": -0.5679, + }, + "LYS": { + "N": -0.3479, + "H": 0.2747, + "CA": -0.24, + "HA": 0.1426, + "CB": -0.0094, + "HB2": 0.0362, + "HB3": 0.0362, + "CG": 0.0187, + "HG2": 0.0103, + "HG3": 0.0103, + "CD": -0.0479, + "HD2": 0.0621, + "HD3": 0.0621, + "CE": -0.0143, + "HE2": 0.1135, + "HE3": 0.1135, + "NZ": -0.3854, + "HZ1": 0.34, + "HZ2": 0.34, + "HZ3": 0.34, + "C": 0.7341, + "O": -0.5894, + }, + "MET": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0237, + "HA": 0.088, + "CB": 0.0342, + "HB2": 0.0241, + "HB3": 0.0241, + "CG": 0.0018, + "HG2": 0.044, + "HG3": 0.044, + "SD": -0.2737, + "CE": -0.0536, + "HE1": 0.0684, + "HE2": 0.0684, + "HE3": 0.0684, + "C": 0.5973, + "O": -0.5679, + }, + "PHE": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0024, + "HA": 0.0978, + "CB": -0.0343, + "HB2": 0.0295, + "HB3": 0.0295, + "CG": 0.0118, + "CD1": -0.1256, + "HD1": 0.133, + "CE1": -0.1704, + "HE1": 0.143, + "CZ": -0.1072, + "HZ": 0.1297, + "CE2": -0.1704, + "HE2": 0.143, + "CD2": -0.1256, + "HD2": 0.133, + "C": 0.5973, + "O": -0.5679, + }, + "PRO": { + "N": -0.2548, + "CD": 0.0192, + "HD2": 0.0391, + "HD3": 0.0391, + "CG": 0.0189, + "HG2": 0.0213, + "HG3": 0.0213, + "CB": -0.007, + "HB2": 0.0253, + "HB3": 0.0253, + "CA": -0.0266, + "HA": 0.0641, + "C": 0.5896, + "O": -0.5748, + }, + "SER": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0249, + "HA": 0.0843, + "CB": 0.2117, + "HB2": 0.0352, + "HB3": 0.0352, + "OG": -0.6546, + "HG": 0.4275, + "C": 0.5973, + "O": -0.5679, + }, + "THR": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0389, + "HA": 0.1007, + "CB": 0.3654, + "HB": 0.0043, + "CG2": -0.2438, + "HG21": 0.0642, + "HG22": 0.0642, + "HG23": 0.0642, + "OG1": -0.6761, + "HG1": 0.4102, + "C": 0.5973, + "O": -0.5679, + }, + "TRP": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0275, + "HA": 0.1123, + "CB": -0.005, + "HB2": 0.0339, + "HB3": 0.0339, + "CG": -0.1415, + "CD1": -0.1638, + "HD1": 0.2062, + "NE1": -0.3418, + "HE1": 0.3412, + "CE2": 0.138, + "CZ2": -0.2601, + "HZ2": 0.1572, + "CH2": -0.1134, + "HH2": 0.1417, + "CZ3": -0.1972, + "HZ3": 0.1447, + "CE3": -0.2387, + "HE3": 0.17, + "CD2": 0.1243, + "C": 0.5973, + "O": -0.5679, + }, + "TYR": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0014, + "HA": 0.0876, + "CB": -0.0152, + "HB2": 0.0295, + "HB3": 0.0295, + "CG": -0.0011, + "CD1": -0.1906, + "HD1": 0.1699, + "CE1": -0.2341, + "HE1": 0.1656, + "CZ": 0.3226, + "OH": -0.5579, + "HH": 0.3992, + "CE2": -0.2341, + "HE2": 0.1656, + "CD2": -0.1906, + "HD2": 0.1699, + "C": 0.5973, + "O": -0.5679, + }, + "VAL": { + "N": -0.4157, + "H": 0.2719, + "CA": -0.0875, + "HA": 0.0969, + "CB": 0.2985, + "HB": -0.0297, + "CG1": -0.3192, + "HG11": 0.0791, + "HG12": 0.0791, + "HG13": 0.0791, + "CG2": -0.3192, + "HG21": 0.0791, + "HG22": 0.0791, + "HG23": 0.0791, + "C": 0.5973, + "O": -0.5679, + }, # nucleic acid charges - "DA": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0431, "H1'": 0.1838, "N9": -0.0268, "C8": 0.1607, "H8": 0.1877, "N7": -0.6175, "C5": 0.0725, "C6": 0.6897, "N6": -0.9123, "H61": 0.4167, "H62": 0.4167, "N1": -0.7624, "C2": 0.5716, "H2": 0.0598, "N3": -0.7417, "C4": 0.38, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DA3": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0431, "H1'": 0.1838, "N9": -0.0268, "C8": 0.1607, "H8": 0.1877, "N7": -0.6175, "C5": 0.0725, "C6": 0.6897, "N6": -0.9123, "H61": 0.4167, "H62": 0.4167, "N1": -0.7624, "C2": 0.5716, "H2": 0.0598, "N3": -0.7417, "C4": 0.38, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DA5": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0431, "H1'": 0.1838, "N9": -0.0268, "C8": 0.1607, "H8": 0.1877, "N7": -0.6175, "C5": 0.0725, "C6": 0.6897, "N6": -0.9123, "H61": 0.4167, "H62": 0.4167, "N1": -0.7624, "C2": 0.5716, "H2": 0.0598, "N3": -0.7417, "C4": 0.38, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DAN": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0431, "H1'": 0.1838, "N9": -0.0268, "C8": 0.1607, "H8": 0.1877, "N7": -0.6175, "C5": 0.0725, "C6": 0.6897, "N6": -0.9123, "H61": 0.4167, "H62": 0.4167, "N1": -0.7624, "C2": 0.5716, "H2": 0.0598, "N3": -0.7417, "C4": 0.38, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DC": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": -0.0116, "H1'": 0.1963, "N1": -0.0339, "C6": -0.0183, "H6": 0.2293, "C5": -0.5222, "H5": 0.1863, "C4": 0.8439, "N4": -0.9773, "H41": 0.4314, "H42": 0.4314, "N3": -0.7748, "C2": 0.7959, "O2": -0.6548, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DC3": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": -0.0116, "H1'": 0.1963, "N1": -0.0339, "C6": -0.0183, "H6": 0.2293, "C5": -0.5222, "H5": 0.1863, "C4": 0.8439, "N4": -0.9773, "H41": 0.4314, "H42": 0.4314, "N3": -0.7748, "C2": 0.7959, "O2": -0.6548, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DC5": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": -0.0116, "H1'": 0.1963, "N1": -0.0339, "C6": -0.0183, "H6": 0.2293, "C5": -0.5222, "H5": 0.1863, "C4": 0.8439, "N4": -0.9773, "H41": 0.4314, "H42": 0.4314, "N3": -0.7748, "C2": 0.7959, "O2": -0.6548, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DCN": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": -0.0116, "H1'": 0.1963, "N1": -0.0339, "C6": -0.0183, "H6": 0.2293, "C5": -0.5222, "H5": 0.1863, "C4": 0.8439, "N4": -0.9773, "H41": 0.4314, "H42": 0.4314, "N3": -0.7748, "C2": 0.7959, "O2": -0.6548, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DG": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0358, "H1'": 0.1746, "N9": 0.0577, "C8": 0.0736, "H8": 0.1997, "N7": -0.5725, "C5": 0.1991, "C6": 0.4918, "O6": -0.5699, "N1": -0.5053, "H1": 0.352, "C2": 0.7432, "N2": -0.923, "H21": 0.4235, "H22": 0.4235, "N3": -0.6636, "C4": 0.1814, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DG3": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0358, "H1'": 0.1746, "N9": 0.0577, "C8": 0.0736, "H8": 0.1997, "N7": -0.5725, "C5": 0.1991, "C6": 0.4918, "O6": -0.5699, "N1": -0.5053, "H1": 0.352, "C2": 0.7432, "N2": -0.923, "H21": 0.4235, "H22": 0.4235, "N3": -0.6636, "C4": 0.1814, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DG5": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0358, "H1'": 0.1746, "N9": 0.0577, "C8": 0.0736, "H8": 0.1997, "N7": -0.5725, "C5": 0.1991, "C6": 0.4918, "O6": -0.5699, "N1": -0.5053, "H1": 0.352, "C2": 0.7432, "N2": -0.923, "H21": 0.4235, "H22": 0.4235, "N3": -0.6636, "C4": 0.1814, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DGN": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.0358, "H1'": 0.1746, "N9": 0.0577, "C8": 0.0736, "H8": 0.1997, "N7": -0.5725, "C5": 0.1991, "C6": 0.4918, "O6": -0.5699, "N1": -0.5053, "H1": 0.352, "C2": 0.7432, "N2": -0.923, "H21": 0.4235, "H22": 0.4235, "N3": -0.6636, "C4": 0.1814, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DT": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.068, "H1'": 0.1804, "N1": -0.0239, "C6": -0.2209, "H6": 0.2607, "C5": 0.0025, "C7": -0.2269, "H71": 0.077, "H72": 0.077, "H73": 0.077, "C4": 0.5194, "O4": -0.5563, "N3": -0.434, "H3": 0.342, "C2": 0.5677, "O2": -0.5881, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DT3": {"P": 1.1659, "O1P": -0.7761, "O2P": -0.7761, "O5'": -0.4954, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.068, "H1'": 0.1804, "N1": -0.0239, "C6": -0.2209, "H6": 0.2607, "C5": 0.0025, "C7": -0.2269, "H71": 0.077, "H72": 0.077, "H73": 0.077, "C4": 0.5194, "O4": -0.5563, "N3": -0.434, "H3": 0.342, "C2": 0.5677, "O2": -0.5881, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "DT5": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.068, "H1'": 0.1804, "N1": -0.0239, "C6": -0.2209, "H6": 0.2607, "C5": 0.0025, "C7": -0.2269, "H71": 0.077, "H72": 0.077, "H73": 0.077, "C4": 0.5194, "O4": -0.5563, "N3": -0.434, "H3": 0.342, "C2": 0.5677, "O2": -0.5881, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.5232}, - "DTN": {"H5T": 0.4422, "O5'": -0.6318, "C5'": -0.0069, "H5'1": 0.0754, "H5'2": 0.0754, "C4'": 0.1629, "H4'": 0.1176, "O4'": -0.3691, "C1'": 0.068, "H1'": 0.1804, "N1": -0.0239, "C6": -0.2209, "H6": 0.2607, "C5": 0.0025, "C7": -0.2269, "H71": 0.077, "H72": 0.077, "H73": 0.077, "C4": 0.5194, "O4": -0.5563, "N3": -0.434, "H3": 0.342, "C2": 0.5677, "O2": -0.5881, "C3'": 0.0713, "H3'": 0.0985, "C2'": -0.0854, "H2'1": 0.0718, "H2'2": 0.0718, "O3'": -0.6549, "H3T": 0.4396}, - "RA": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0394, "H1'": 0.2007, "N9": -0.0251, "C8": 0.2006, "H8": 0.1553, "N7": -0.6073, "C5": 0.0515, "C6": 0.7009, "N6": -0.9019, "H61": 0.4115, "H62": 0.4115, "N1": -0.7615, "C2": 0.5875, "H2": 0.0473, "N3": -0.6997, "C4": 0.3053, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RA3": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0394, "H1'": 0.2007, "N9": -0.0251, "C8": 0.2006, "H8": 0.1553, "N7": -0.6073, "C5": 0.0515, "C6": 0.7009, "N6": -0.9019, "H61": 0.4115, "H62": 0.4115, "N1": -0.7615, "C2": 0.5875, "H2": 0.0473, "N3": -0.6997, "C4": 0.3053, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RA5": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0394, "H1'": 0.2007, "N9": -0.0251, "C8": 0.2006, "H8": 0.1553, "N7": -0.6073, "C5": 0.0515, "C6": 0.7009, "N6": -0.9019, "H61": 0.4115, "H62": 0.4115, "N1": -0.7615, "C2": 0.5875, "H2": 0.0473, "N3": -0.6997, "C4": 0.3053, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RAN": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0394, "H1'": 0.2007, "N9": -0.0251, "C8": 0.2006, "H8": 0.1553, "N7": -0.6073, "C5": 0.0515, "C6": 0.7009, "N6": -0.9019, "H61": 0.4115, "H62": 0.4115, "N1": -0.7615, "C2": 0.5875, "H2": 0.0473, "N3": -0.6997, "C4": 0.3053, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RC": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0066, "H1'": 0.2029, "N1": -0.0484, "C6": 0.0053, "H6": 0.1958, "C5": -0.5215, "H5": 0.1928, "C4": 0.8185, "N4": -0.953, "H41": 0.4234, "H42": 0.4234, "N3": -0.7584, "C2": 0.7538, "O2": -0.6252, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RC3": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0066, "H1'": 0.2029, "N1": -0.0484, "C6": 0.0053, "H6": 0.1958, "C5": -0.5215, "H5": 0.1928, "C4": 0.8185, "N4": -0.953, "H41": 0.4234, "H42": 0.4234, "N3": -0.7584, "C2": 0.7538, "O2": -0.6252, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RC5": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0066, "H1'": 0.2029, "N1": -0.0484, "C6": 0.0053, "H6": 0.1958, "C5": -0.5215, "H5": 0.1928, "C4": 0.8185, "N4": -0.953, "H41": 0.4234, "H42": 0.4234, "N3": -0.7584, "C2": 0.7538, "O2": -0.6252, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RCN": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0066, "H1'": 0.2029, "N1": -0.0484, "C6": 0.0053, "H6": 0.1958, "C5": -0.5215, "H5": 0.1928, "C4": 0.8185, "N4": -0.953, "H41": 0.4234, "H42": 0.4234, "N3": -0.7584, "C2": 0.7538, "O2": -0.6252, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RG": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0191, "H1'": 0.2006, "N9": 0.0492, "C8": 0.1374, "H8": 0.164, "N7": -0.5709, "C5": 0.1744, "C6": 0.477, "O6": -0.5597, "N1": -0.4787, "H1": 0.3424, "C2": 0.7657, "N2": -0.9672, "H21": 0.4364, "H22": 0.4364, "N3": -0.6323, "C4": 0.1222, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RG3": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0191, "H1'": 0.2006, "N9": 0.0492, "C8": 0.1374, "H8": 0.164, "N7": -0.5709, "C5": 0.1744, "C6": 0.477, "O6": -0.5597, "N1": -0.4787, "H1": 0.3424, "C2": 0.7657, "N2": -0.9672, "H21": 0.4364, "H22": 0.4364, "N3": -0.6323, "C4": 0.1222, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RG5": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0191, "H1'": 0.2006, "N9": 0.0492, "C8": 0.1374, "H8": 0.164, "N7": -0.5709, "C5": 0.1744, "C6": 0.477, "O6": -0.5597, "N1": -0.4787, "H1": 0.3424, "C2": 0.7657, "N2": -0.9672, "H21": 0.4364, "H22": 0.4364, "N3": -0.6323, "C4": 0.1222, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RGN": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0191, "H1'": 0.2006, "N9": 0.0492, "C8": 0.1374, "H8": 0.164, "N7": -0.5709, "C5": 0.1744, "C6": 0.477, "O6": -0.5597, "N1": -0.4787, "H1": 0.3424, "C2": 0.7657, "N2": -0.9672, "H21": 0.4364, "H22": 0.4364, "N3": -0.6323, "C4": 0.1222, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RU": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0674, "H1'": 0.1824, "N1": 0.0418, "C6": -0.1126, "H6": 0.2188, "C5": -0.3635, "H5": 0.1811, "C4": 0.5952, "O4": -0.5761, "N3": -0.3549, "H3": 0.3154, "C2": 0.4687, "O2": -0.5477, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RU3": {"P": 1.1662, "O1P": -0.776, "O2P": -0.776, "O5'": -0.4989, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0674, "H1'": 0.1824, "N1": 0.0418, "C6": -0.1126, "H6": 0.2188, "C5": -0.3635, "H5": 0.1811, "C4": 0.5952, "O4": -0.5761, "N3": -0.3549, "H3": 0.3154, "C2": 0.4687, "O2": -0.5477, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376}, - "RU5": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0674, "H1'": 0.1824, "N1": 0.0418, "C6": -0.1126, "H6": 0.2188, "C5": -0.3635, "H5": 0.1811, "C4": 0.5952, "O4": -0.5761, "N3": -0.3549, "H3": 0.3154, "C2": 0.4687, "O2": -0.5477, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.5246}, - "RUN": {"H5T": 0.4295, "O5'": -0.6223, "C5'": 0.0558, "H5'1": 0.0679, "H5'2": 0.0679, "C4'": 0.1065, "H4'": 0.1174, "O4'": -0.3548, "C1'": 0.0674, "H1'": 0.1824, "N1": 0.0418, "C6": -0.1126, "H6": 0.2188, "C5": -0.3635, "H5": 0.1811, "C4": 0.5952, "O4": -0.5761, "N3": -0.3549, "H3": 0.3154, "C2": 0.4687, "O2": -0.5477, "C3'": 0.2022, "H3'": 0.0615, "C2'": 0.067, "H2'1": 0.0972, "O2'": -0.6139, "HO'2": 0.4186, "O3'": -0.6541, "H3T": 0.4376} + "DA": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0431, + "H1'": 0.1838, + "N9": -0.0268, + "C8": 0.1607, + "H8": 0.1877, + "N7": -0.6175, + "C5": 0.0725, + "C6": 0.6897, + "N6": -0.9123, + "H61": 0.4167, + "H62": 0.4167, + "N1": -0.7624, + "C2": 0.5716, + "H2": 0.0598, + "N3": -0.7417, + "C4": 0.38, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DA3": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0431, + "H1'": 0.1838, + "N9": -0.0268, + "C8": 0.1607, + "H8": 0.1877, + "N7": -0.6175, + "C5": 0.0725, + "C6": 0.6897, + "N6": -0.9123, + "H61": 0.4167, + "H62": 0.4167, + "N1": -0.7624, + "C2": 0.5716, + "H2": 0.0598, + "N3": -0.7417, + "C4": 0.38, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DA5": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0431, + "H1'": 0.1838, + "N9": -0.0268, + "C8": 0.1607, + "H8": 0.1877, + "N7": -0.6175, + "C5": 0.0725, + "C6": 0.6897, + "N6": -0.9123, + "H61": 0.4167, + "H62": 0.4167, + "N1": -0.7624, + "C2": 0.5716, + "H2": 0.0598, + "N3": -0.7417, + "C4": 0.38, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DAN": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0431, + "H1'": 0.1838, + "N9": -0.0268, + "C8": 0.1607, + "H8": 0.1877, + "N7": -0.6175, + "C5": 0.0725, + "C6": 0.6897, + "N6": -0.9123, + "H61": 0.4167, + "H62": 0.4167, + "N1": -0.7624, + "C2": 0.5716, + "H2": 0.0598, + "N3": -0.7417, + "C4": 0.38, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DC": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": -0.0116, + "H1'": 0.1963, + "N1": -0.0339, + "C6": -0.0183, + "H6": 0.2293, + "C5": -0.5222, + "H5": 0.1863, + "C4": 0.8439, + "N4": -0.9773, + "H41": 0.4314, + "H42": 0.4314, + "N3": -0.7748, + "C2": 0.7959, + "O2": -0.6548, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DC3": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": -0.0116, + "H1'": 0.1963, + "N1": -0.0339, + "C6": -0.0183, + "H6": 0.2293, + "C5": -0.5222, + "H5": 0.1863, + "C4": 0.8439, + "N4": -0.9773, + "H41": 0.4314, + "H42": 0.4314, + "N3": -0.7748, + "C2": 0.7959, + "O2": -0.6548, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DC5": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": -0.0116, + "H1'": 0.1963, + "N1": -0.0339, + "C6": -0.0183, + "H6": 0.2293, + "C5": -0.5222, + "H5": 0.1863, + "C4": 0.8439, + "N4": -0.9773, + "H41": 0.4314, + "H42": 0.4314, + "N3": -0.7748, + "C2": 0.7959, + "O2": -0.6548, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DCN": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": -0.0116, + "H1'": 0.1963, + "N1": -0.0339, + "C6": -0.0183, + "H6": 0.2293, + "C5": -0.5222, + "H5": 0.1863, + "C4": 0.8439, + "N4": -0.9773, + "H41": 0.4314, + "H42": 0.4314, + "N3": -0.7748, + "C2": 0.7959, + "O2": -0.6548, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DG": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0358, + "H1'": 0.1746, + "N9": 0.0577, + "C8": 0.0736, + "H8": 0.1997, + "N7": -0.5725, + "C5": 0.1991, + "C6": 0.4918, + "O6": -0.5699, + "N1": -0.5053, + "H1": 0.352, + "C2": 0.7432, + "N2": -0.923, + "H21": 0.4235, + "H22": 0.4235, + "N3": -0.6636, + "C4": 0.1814, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DG3": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0358, + "H1'": 0.1746, + "N9": 0.0577, + "C8": 0.0736, + "H8": 0.1997, + "N7": -0.5725, + "C5": 0.1991, + "C6": 0.4918, + "O6": -0.5699, + "N1": -0.5053, + "H1": 0.352, + "C2": 0.7432, + "N2": -0.923, + "H21": 0.4235, + "H22": 0.4235, + "N3": -0.6636, + "C4": 0.1814, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DG5": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0358, + "H1'": 0.1746, + "N9": 0.0577, + "C8": 0.0736, + "H8": 0.1997, + "N7": -0.5725, + "C5": 0.1991, + "C6": 0.4918, + "O6": -0.5699, + "N1": -0.5053, + "H1": 0.352, + "C2": 0.7432, + "N2": -0.923, + "H21": 0.4235, + "H22": 0.4235, + "N3": -0.6636, + "C4": 0.1814, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DGN": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.0358, + "H1'": 0.1746, + "N9": 0.0577, + "C8": 0.0736, + "H8": 0.1997, + "N7": -0.5725, + "C5": 0.1991, + "C6": 0.4918, + "O6": -0.5699, + "N1": -0.5053, + "H1": 0.352, + "C2": 0.7432, + "N2": -0.923, + "H21": 0.4235, + "H22": 0.4235, + "N3": -0.6636, + "C4": 0.1814, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DT": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.068, + "H1'": 0.1804, + "N1": -0.0239, + "C6": -0.2209, + "H6": 0.2607, + "C5": 0.0025, + "C7": -0.2269, + "H71": 0.077, + "H72": 0.077, + "H73": 0.077, + "C4": 0.5194, + "O4": -0.5563, + "N3": -0.434, + "H3": 0.342, + "C2": 0.5677, + "O2": -0.5881, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DT3": { + "P": 1.1659, + "O1P": -0.7761, + "O2P": -0.7761, + "O5'": -0.4954, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.068, + "H1'": 0.1804, + "N1": -0.0239, + "C6": -0.2209, + "H6": 0.2607, + "C5": 0.0025, + "C7": -0.2269, + "H71": 0.077, + "H72": 0.077, + "H73": 0.077, + "C4": 0.5194, + "O4": -0.5563, + "N3": -0.434, + "H3": 0.342, + "C2": 0.5677, + "O2": -0.5881, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "DT5": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.068, + "H1'": 0.1804, + "N1": -0.0239, + "C6": -0.2209, + "H6": 0.2607, + "C5": 0.0025, + "C7": -0.2269, + "H71": 0.077, + "H72": 0.077, + "H73": 0.077, + "C4": 0.5194, + "O4": -0.5563, + "N3": -0.434, + "H3": 0.342, + "C2": 0.5677, + "O2": -0.5881, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.5232, + }, + "DTN": { + "H5T": 0.4422, + "O5'": -0.6318, + "C5'": -0.0069, + "H5'1": 0.0754, + "H5'2": 0.0754, + "C4'": 0.1629, + "H4'": 0.1176, + "O4'": -0.3691, + "C1'": 0.068, + "H1'": 0.1804, + "N1": -0.0239, + "C6": -0.2209, + "H6": 0.2607, + "C5": 0.0025, + "C7": -0.2269, + "H71": 0.077, + "H72": 0.077, + "H73": 0.077, + "C4": 0.5194, + "O4": -0.5563, + "N3": -0.434, + "H3": 0.342, + "C2": 0.5677, + "O2": -0.5881, + "C3'": 0.0713, + "H3'": 0.0985, + "C2'": -0.0854, + "H2'1": 0.0718, + "H2'2": 0.0718, + "O3'": -0.6549, + "H3T": 0.4396, + }, + "RA": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0394, + "H1'": 0.2007, + "N9": -0.0251, + "C8": 0.2006, + "H8": 0.1553, + "N7": -0.6073, + "C5": 0.0515, + "C6": 0.7009, + "N6": -0.9019, + "H61": 0.4115, + "H62": 0.4115, + "N1": -0.7615, + "C2": 0.5875, + "H2": 0.0473, + "N3": -0.6997, + "C4": 0.3053, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RA3": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0394, + "H1'": 0.2007, + "N9": -0.0251, + "C8": 0.2006, + "H8": 0.1553, + "N7": -0.6073, + "C5": 0.0515, + "C6": 0.7009, + "N6": -0.9019, + "H61": 0.4115, + "H62": 0.4115, + "N1": -0.7615, + "C2": 0.5875, + "H2": 0.0473, + "N3": -0.6997, + "C4": 0.3053, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RA5": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0394, + "H1'": 0.2007, + "N9": -0.0251, + "C8": 0.2006, + "H8": 0.1553, + "N7": -0.6073, + "C5": 0.0515, + "C6": 0.7009, + "N6": -0.9019, + "H61": 0.4115, + "H62": 0.4115, + "N1": -0.7615, + "C2": 0.5875, + "H2": 0.0473, + "N3": -0.6997, + "C4": 0.3053, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RAN": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0394, + "H1'": 0.2007, + "N9": -0.0251, + "C8": 0.2006, + "H8": 0.1553, + "N7": -0.6073, + "C5": 0.0515, + "C6": 0.7009, + "N6": -0.9019, + "H61": 0.4115, + "H62": 0.4115, + "N1": -0.7615, + "C2": 0.5875, + "H2": 0.0473, + "N3": -0.6997, + "C4": 0.3053, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RC": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0066, + "H1'": 0.2029, + "N1": -0.0484, + "C6": 0.0053, + "H6": 0.1958, + "C5": -0.5215, + "H5": 0.1928, + "C4": 0.8185, + "N4": -0.953, + "H41": 0.4234, + "H42": 0.4234, + "N3": -0.7584, + "C2": 0.7538, + "O2": -0.6252, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RC3": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0066, + "H1'": 0.2029, + "N1": -0.0484, + "C6": 0.0053, + "H6": 0.1958, + "C5": -0.5215, + "H5": 0.1928, + "C4": 0.8185, + "N4": -0.953, + "H41": 0.4234, + "H42": 0.4234, + "N3": -0.7584, + "C2": 0.7538, + "O2": -0.6252, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RC5": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0066, + "H1'": 0.2029, + "N1": -0.0484, + "C6": 0.0053, + "H6": 0.1958, + "C5": -0.5215, + "H5": 0.1928, + "C4": 0.8185, + "N4": -0.953, + "H41": 0.4234, + "H42": 0.4234, + "N3": -0.7584, + "C2": 0.7538, + "O2": -0.6252, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RCN": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0066, + "H1'": 0.2029, + "N1": -0.0484, + "C6": 0.0053, + "H6": 0.1958, + "C5": -0.5215, + "H5": 0.1928, + "C4": 0.8185, + "N4": -0.953, + "H41": 0.4234, + "H42": 0.4234, + "N3": -0.7584, + "C2": 0.7538, + "O2": -0.6252, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RG": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0191, + "H1'": 0.2006, + "N9": 0.0492, + "C8": 0.1374, + "H8": 0.164, + "N7": -0.5709, + "C5": 0.1744, + "C6": 0.477, + "O6": -0.5597, + "N1": -0.4787, + "H1": 0.3424, + "C2": 0.7657, + "N2": -0.9672, + "H21": 0.4364, + "H22": 0.4364, + "N3": -0.6323, + "C4": 0.1222, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RG3": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0191, + "H1'": 0.2006, + "N9": 0.0492, + "C8": 0.1374, + "H8": 0.164, + "N7": -0.5709, + "C5": 0.1744, + "C6": 0.477, + "O6": -0.5597, + "N1": -0.4787, + "H1": 0.3424, + "C2": 0.7657, + "N2": -0.9672, + "H21": 0.4364, + "H22": 0.4364, + "N3": -0.6323, + "C4": 0.1222, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RG5": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0191, + "H1'": 0.2006, + "N9": 0.0492, + "C8": 0.1374, + "H8": 0.164, + "N7": -0.5709, + "C5": 0.1744, + "C6": 0.477, + "O6": -0.5597, + "N1": -0.4787, + "H1": 0.3424, + "C2": 0.7657, + "N2": -0.9672, + "H21": 0.4364, + "H22": 0.4364, + "N3": -0.6323, + "C4": 0.1222, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RGN": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0191, + "H1'": 0.2006, + "N9": 0.0492, + "C8": 0.1374, + "H8": 0.164, + "N7": -0.5709, + "C5": 0.1744, + "C6": 0.477, + "O6": -0.5597, + "N1": -0.4787, + "H1": 0.3424, + "C2": 0.7657, + "N2": -0.9672, + "H21": 0.4364, + "H22": 0.4364, + "N3": -0.6323, + "C4": 0.1222, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RU": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0674, + "H1'": 0.1824, + "N1": 0.0418, + "C6": -0.1126, + "H6": 0.2188, + "C5": -0.3635, + "H5": 0.1811, + "C4": 0.5952, + "O4": -0.5761, + "N3": -0.3549, + "H3": 0.3154, + "C2": 0.4687, + "O2": -0.5477, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RU3": { + "P": 1.1662, + "O1P": -0.776, + "O2P": -0.776, + "O5'": -0.4989, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0674, + "H1'": 0.1824, + "N1": 0.0418, + "C6": -0.1126, + "H6": 0.2188, + "C5": -0.3635, + "H5": 0.1811, + "C4": 0.5952, + "O4": -0.5761, + "N3": -0.3549, + "H3": 0.3154, + "C2": 0.4687, + "O2": -0.5477, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, + "RU5": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0674, + "H1'": 0.1824, + "N1": 0.0418, + "C6": -0.1126, + "H6": 0.2188, + "C5": -0.3635, + "H5": 0.1811, + "C4": 0.5952, + "O4": -0.5761, + "N3": -0.3549, + "H3": 0.3154, + "C2": 0.4687, + "O2": -0.5477, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.5246, + }, + "RUN": { + "H5T": 0.4295, + "O5'": -0.6223, + "C5'": 0.0558, + "H5'1": 0.0679, + "H5'2": 0.0679, + "C4'": 0.1065, + "H4'": 0.1174, + "O4'": -0.3548, + "C1'": 0.0674, + "H1'": 0.1824, + "N1": 0.0418, + "C6": -0.1126, + "H6": 0.2188, + "C5": -0.3635, + "H5": 0.1811, + "C4": 0.5952, + "O4": -0.5761, + "N3": -0.3549, + "H3": 0.3154, + "C2": 0.4687, + "O2": -0.5477, + "C3'": 0.2022, + "H3'": 0.0615, + "C2'": 0.067, + "H2'1": 0.0972, + "O2'": -0.6139, + "HO'2": 0.4186, + "O3'": -0.6541, + "H3T": 0.4376, + }, } lipophobicity = { # standard amino acids - 'ALA': {'C': -0.61, 'CA': 0.02, 'CB': 0.62, 'O': -0.58, 'N': -0.49, 'H': -0.5, 'HA': -0.25, 'HB1': 0.0, 'HB2': 0.0, 'HB3': 0.0, 'OXT': 0.49}, - 'ARG': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD': 0.45, 'CG': 0.45, 'CZ': -0.61, 'N': -0.49, 'NE': -0.49, 'NH1': -0.14, 'NH2': -0.69, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'HD2': -0.25, 'HD3': -0.25, 'HE': -0.5, 'HH11': -0.5, 'HH12': -0.5, 'HH21': -0.5, 'HH22': -0.5, '1HH1': -0.5, '2HH1': -0.5, '1HH2': -0.5, '2HH2': -0.5, 'OXT': 0.49}, - 'ASN': {'C': -0.61, 'CA': 0.02, 'CB': 0.02, 'CG': -0.61, 'N': -0.49, 'ND2': -0.14, 'O': -0.58, 'OD1': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HD21': -0.5, 'HD22': -0.5, '1HD2': -0.5, '2HD2': -0.5, 'OXT': 0.49}, - 'ASP': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CG': -0.61, 'N': -0.49, 'O': -0.58, 'OD1': -0.58, 'OD2': 0.49, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'OXT': 0.49}, - 'CYS': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'N': -0.49, 'O': -0.58, 'SG': 0.29, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'OXT': 0.49}, - 'GLN': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD': -0.54, 'CG': 0.45, 'N': -0.49, 'NE2': -0.14, 'O': -0.58, 'OE1': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'HE21': -0.5, 'HE22': -0.5, '1HE2': -0.5, '2HE2': -0.5, 'OXT': 0.49}, - 'GLU': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD': -0.54, 'CG': 0.45, 'N': -0.49, 'O': -0.58, 'OE1': -0.58, 'OE2': 0.49, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'OXT': 0.49}, - 'GLY': {'C': -0.61, 'CA': 0.45, 'O': -0.58, 'N': -0.57, 'H': -0.5, 'HA': -0.25, 'HA2': 0.0, 'HA3': 0.0, 'OXT': 0.49}, - 'HIS': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD2': 0.31, 'CE1': 0.31, 'CG': 0.1, 'N': -0.49, 'ND1': 0.08, 'NE2': -1.14, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HD1': -0.5, 'HD2': -0.25, 'HE1': -0.25, 'OXT': 0.49}, - 'ILE': {'C': -0.61, 'CA': 0.02, 'CB': 0.02, 'CD': 0.63, 'CD1': 0.63, 'CG1': 0.45, 'CG2': 0.63, 'N': -0.49, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB': 0.0, 'HG12': 0.0, 'HG13': 0.0, 'HG21': 0.0, 'HG22': 0.0, 'HG23': 0.0, 'HD11': 0.0, 'HD12': 0.0, 'HD13': 0.0, '2HG1': 0.0, '3HG1': 0.0, '1HG2': 0.0, '2HG2': 0.0, '3HG2': 0.0, '1HD1': 0.0, '2HD1': 0.0, '3HD1': 0.0, 'OXT': 0.49}, - 'LEU': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD1': 0.63, 'CD2': 0.63, 'CG': 0.02, 'N': -0.49, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG': 0.0, 'HD11': 0.0, 'HD12': 0.0, 'HD13': 0.0, 'HD21': 0.0, 'HD22': 0.0, 'HD23': 0.0, '1HD1': 0.0, '2HD1': 0.0, '3HD1': 0.0, '1HD2': 0.0, '2HD2': 0.0, '3HD2': 0.0, 'OXT': 0.49}, - 'LYS': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD': 0.45, 'CE': 0.45, 'CG': 0.45, 'N': -0.49, 'NZ': -1.07, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'HD2': 0.0, 'HD3': 0.0, 'HE2': -0.2, 'HE3': -0.2, 'HZ1': -0.5, 'HZ2': -0.5, 'HZ3': -0.5, 'OXT': 0.49, }, - 'MET': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CE': 0.63, 'CG': 0.45, 'N': -0.49, 'O': -0.58, 'SD': -0.30, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'HE1': 0.0, 'HE2': 0.0, 'HE3': -0.5, 'OXT': 0.49, }, - 'PHE': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD1': 0.31, 'CD2': 0.31, 'CE1': 0.31, 'CE2': 0.31, 'CG': 0.1, 'CZ': 0.31, 'N': -0.49, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HD1': 0.0, 'HD2': 0.0, 'HE1': 0.0, 'HE2': 0.0, 'HZ': 0.0, 'OXT': 0.49}, - 'PRO': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD': 0.45, 'CG': 0.45, 'N': -0.92, 'O': -0.58, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG2': 0.0, 'HG3': 0.0, 'HD2': -0.2, 'HD3': -0.2, 'OXT': 0.49, }, - 'SER': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'N': -0.49, 'O': -0.58, 'OG': -0.99, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HG': 0.0, 'OXT': 0.49, }, - 'THR': {'C': -0.61, 'CA': 0.02, 'CB': 0.02, 'CG2': 0.62, 'N': -0.49, 'O': -0.58, 'OG1': -0.9, 'H': -0.5, 'HA': -0.25, 'HB': 0.0, 'HG1': 0.0, 'HG21': 0.0, 'HG22': 0.0, 'HG23': 0.0, '1HG2': 0.0, '2HG2': 0.0, '3HG2': 0.0, 'OXT': 0.49, }, - 'TRP': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD1': 0.31, 'CD2': 0.25, 'CE2': 0.25, 'CE3': 0.31, 'CG': 0.1, 'CH2': 0.31, 'CZ2': 0.31, 'CZ3': 0.31, 'N': -0.49, 'NE1': 0.08, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HE1': -0.5, 'HD1': -0.2, 'HE3': 0.0, 'HZ2': 0.0, 'HZ3': 0.0, 'HH2': 0.0, 'OXT': 0.49, }, - 'TYR': {'C': -0.61, 'CA': 0.02, 'CB': 0.45, 'CD1': 0.31, 'CD2': 0.31, 'CE1': 0.31, 'CE2': 0.31, 'CG': 0.1, 'CZ': 0.1, 'N': -0.49, 'O': -0.58, 'OH': -0.17, 'H': -0.5, 'HA': -0.25, 'HB2': 0.0, 'HB3': 0.0, 'HD1': 0.0, 'HD2': 0.0, 'HE1': 0.0, 'HE2': 0.0, 'HH': 0.0, 'OXT': 0.49, }, - 'VAL': {'C': -0.61, 'CA': 0.02, 'CB': 0.02, 'CG1': 0.62, 'CG2': 0.62, 'N': -0.49, 'O': -0.58, 'H': -0.5, 'HA': -0.25, 'HB': 0.0, 'HG11': 0.0, 'HG12': 0.0, 'HG13': 0.0, 'HG21': 0.0, 'HG22': 0.0, 'HG23': 0.0, '1HG1': 0.0, '2HG1': 0.0, '3HG1': 0.0, '1HG2': 0.0, '2HG2': 0.0, '3HG2': 0.0, 'OXT': 0.49, }, - + "ALA": { + "C": -0.61, + "CA": 0.02, + "CB": 0.62, + "O": -0.58, + "N": -0.49, + "H": -0.5, + "HA": -0.25, + "HB1": 0.0, + "HB2": 0.0, + "HB3": 0.0, + "OXT": 0.49, + }, + "ARG": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD": 0.45, + "CG": 0.45, + "CZ": -0.61, + "N": -0.49, + "NE": -0.49, + "NH1": -0.14, + "NH2": -0.69, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "HD2": -0.25, + "HD3": -0.25, + "HE": -0.5, + "HH11": -0.5, + "HH12": -0.5, + "HH21": -0.5, + "HH22": -0.5, + "1HH1": -0.5, + "2HH1": -0.5, + "1HH2": -0.5, + "2HH2": -0.5, + "OXT": 0.49, + }, + "ASN": { + "C": -0.61, + "CA": 0.02, + "CB": 0.02, + "CG": -0.61, + "N": -0.49, + "ND2": -0.14, + "O": -0.58, + "OD1": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HD21": -0.5, + "HD22": -0.5, + "1HD2": -0.5, + "2HD2": -0.5, + "OXT": 0.49, + }, + "ASP": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CG": -0.61, + "N": -0.49, + "O": -0.58, + "OD1": -0.58, + "OD2": 0.49, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "OXT": 0.49, + }, + "CYS": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "N": -0.49, + "O": -0.58, + "SG": 0.29, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "OXT": 0.49, + }, + "GLN": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD": -0.54, + "CG": 0.45, + "N": -0.49, + "NE2": -0.14, + "O": -0.58, + "OE1": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "HE21": -0.5, + "HE22": -0.5, + "1HE2": -0.5, + "2HE2": -0.5, + "OXT": 0.49, + }, + "GLU": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD": -0.54, + "CG": 0.45, + "N": -0.49, + "O": -0.58, + "OE1": -0.58, + "OE2": 0.49, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "OXT": 0.49, + }, + "GLY": { + "C": -0.61, + "CA": 0.45, + "O": -0.58, + "N": -0.57, + "H": -0.5, + "HA": -0.25, + "HA2": 0.0, + "HA3": 0.0, + "OXT": 0.49, + }, + "HIS": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD2": 0.31, + "CE1": 0.31, + "CG": 0.1, + "N": -0.49, + "ND1": 0.08, + "NE2": -1.14, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HD1": -0.5, + "HD2": -0.25, + "HE1": -0.25, + "OXT": 0.49, + }, + "ILE": { + "C": -0.61, + "CA": 0.02, + "CB": 0.02, + "CD": 0.63, + "CD1": 0.63, + "CG1": 0.45, + "CG2": 0.63, + "N": -0.49, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB": 0.0, + "HG12": 0.0, + "HG13": 0.0, + "HG21": 0.0, + "HG22": 0.0, + "HG23": 0.0, + "HD11": 0.0, + "HD12": 0.0, + "HD13": 0.0, + "2HG1": 0.0, + "3HG1": 0.0, + "1HG2": 0.0, + "2HG2": 0.0, + "3HG2": 0.0, + "1HD1": 0.0, + "2HD1": 0.0, + "3HD1": 0.0, + "OXT": 0.49, + }, + "LEU": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD1": 0.63, + "CD2": 0.63, + "CG": 0.02, + "N": -0.49, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG": 0.0, + "HD11": 0.0, + "HD12": 0.0, + "HD13": 0.0, + "HD21": 0.0, + "HD22": 0.0, + "HD23": 0.0, + "1HD1": 0.0, + "2HD1": 0.0, + "3HD1": 0.0, + "1HD2": 0.0, + "2HD2": 0.0, + "3HD2": 0.0, + "OXT": 0.49, + }, + "LYS": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD": 0.45, + "CE": 0.45, + "CG": 0.45, + "N": -0.49, + "NZ": -1.07, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "HD2": 0.0, + "HD3": 0.0, + "HE2": -0.2, + "HE3": -0.2, + "HZ1": -0.5, + "HZ2": -0.5, + "HZ3": -0.5, + "OXT": 0.49, + }, + "MET": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CE": 0.63, + "CG": 0.45, + "N": -0.49, + "O": -0.58, + "SD": -0.30, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "HE1": 0.0, + "HE2": 0.0, + "HE3": -0.5, + "OXT": 0.49, + }, + "PHE": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD1": 0.31, + "CD2": 0.31, + "CE1": 0.31, + "CE2": 0.31, + "CG": 0.1, + "CZ": 0.31, + "N": -0.49, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HD1": 0.0, + "HD2": 0.0, + "HE1": 0.0, + "HE2": 0.0, + "HZ": 0.0, + "OXT": 0.49, + }, + "PRO": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD": 0.45, + "CG": 0.45, + "N": -0.92, + "O": -0.58, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG2": 0.0, + "HG3": 0.0, + "HD2": -0.2, + "HD3": -0.2, + "OXT": 0.49, + }, + "SER": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "N": -0.49, + "O": -0.58, + "OG": -0.99, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HG": 0.0, + "OXT": 0.49, + }, + "THR": { + "C": -0.61, + "CA": 0.02, + "CB": 0.02, + "CG2": 0.62, + "N": -0.49, + "O": -0.58, + "OG1": -0.9, + "H": -0.5, + "HA": -0.25, + "HB": 0.0, + "HG1": 0.0, + "HG21": 0.0, + "HG22": 0.0, + "HG23": 0.0, + "1HG2": 0.0, + "2HG2": 0.0, + "3HG2": 0.0, + "OXT": 0.49, + }, + "TRP": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD1": 0.31, + "CD2": 0.25, + "CE2": 0.25, + "CE3": 0.31, + "CG": 0.1, + "CH2": 0.31, + "CZ2": 0.31, + "CZ3": 0.31, + "N": -0.49, + "NE1": 0.08, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HE1": -0.5, + "HD1": -0.2, + "HE3": 0.0, + "HZ2": 0.0, + "HZ3": 0.0, + "HH2": 0.0, + "OXT": 0.49, + }, + "TYR": { + "C": -0.61, + "CA": 0.02, + "CB": 0.45, + "CD1": 0.31, + "CD2": 0.31, + "CE1": 0.31, + "CE2": 0.31, + "CG": 0.1, + "CZ": 0.1, + "N": -0.49, + "O": -0.58, + "OH": -0.17, + "H": -0.5, + "HA": -0.25, + "HB2": 0.0, + "HB3": 0.0, + "HD1": 0.0, + "HD2": 0.0, + "HE1": 0.0, + "HE2": 0.0, + "HH": 0.0, + "OXT": 0.49, + }, + "VAL": { + "C": -0.61, + "CA": 0.02, + "CB": 0.02, + "CG1": 0.62, + "CG2": 0.62, + "N": -0.49, + "O": -0.58, + "H": -0.5, + "HA": -0.25, + "HB": 0.0, + "HG11": 0.0, + "HG12": 0.0, + "HG13": 0.0, + "HG21": 0.0, + "HG22": 0.0, + "HG23": 0.0, + "1HG1": 0.0, + "2HG1": 0.0, + "3HG1": 0.0, + "1HG2": 0.0, + "2HG2": 0.0, + "3HG2": 0.0, + "OXT": 0.49, + }, # other potential residues - 'CA': {'CA': -1.0}, - 'NAG': {'C1': 0.02, 'C2': 0.02, 'C3': 0.02, 'C4': 0.02, 'C5': 0.02, 'C6': 0.31, 'C7': -0.61, 'C8': 0.62, 'O1': -0.92, 'O2': -0.92, 'O3': -0.92, 'O4': -0.92, 'O5': -1.14, 'O6': -0.99, 'O7': -0.58, 'N2': -0.49, 'H2': -0.25, 'HN2': -0.5, }, - 'NDG': {'C1': 0.02, 'C2': 0.02, 'C3': 0.02, 'C4': 0.02, 'C5': 0.02, 'C6': 0.031, 'C7': -0.61, 'C8': 0.62, 'O1L': -0.9, 'O3': -0.92, 'O4': -0.92, 'O': -1.14, 'O6': -0.99, 'O7': -0.58, 'N2': -0.29, }, - 'BMA': {'C1': 0.02, 'C2': 0.02, 'C3': 0.02, 'C4': 0.02, 'C5': 0.02, 'C6': 0.31, 'O1': -0.92, 'O2': -0.92, 'O3': -0.92, 'O4': -0.92, 'O5': -1.14, 'O6': -0.58, }, - 'MAN': {'C1': 0.02, 'C2': 0.02, 'C3': 0.02, 'C4': 0.02, 'C5': 0.02, 'C6': 0.31, 'O1': -0.92, 'O2': -0.92, 'O3': -0.92, 'O4': -0.92, 'O5': -1.14, 'O6': -0.58, }, - 'GAL': {'C1': 0.02, 'C2': 0.02, 'C3': 0.02, 'C4': 0.02, 'C5': 0.02, 'C6': 0.31, 'O1': -0.92, 'O2': -0.92, 'O3': -0.92, 'O4': -0.92, 'O5': -1.14, 'O6': -0.58, }, - 'NAN': {'C1': -0.61, 'C2': 0.02, 'C3': 0.62, 'C4': 0.02, 'C5': 0.02, 'C6': 0.02, 'C7': 0.02, 'C8': 0.02, 'C9': 0.31, 'C10': -0.6, 'C11': 0.62, 'O1A': -0.2, 'O1B': -0.4, 'O2': -0.92, 'O4': -0.92, 'O6': -1.14, 'O7': -0.92, 'O8': -0.92, 'O9': -0.7, 'O10': -0.2, 'N5': -0.49, 'NH5': -0.5, }, - 'DG': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.45, "C3'": 0.02, "O3'": -0.92, 'N9': -1.66, 'C8': 0.31, 'N7': -0.55, 'C5': 0.25, 'C6': 0.1, 'O6': -0.58, 'N1': -0.49, 'C2': 0.1, 'N2': -0.6, 'N3': -0.07, 'C4': -0.25, }, - 'DA': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.45, "C3'": 0.02, "O3'": -0.92, 'N9': -1.66, 'C8': 0.31, 'N7': -0.55, 'C5': 0.25, 'C6': 0.1, 'N6': -0.6, 'N1': -0.49, 'C2': 0.31, 'N2': -0.6, 'N3': -0.07, 'C4': -0.25, }, - 'DC': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.45, "C3'": 0.02, "O3'": -0.92, 'N1': -1.66, 'C2': 0.1, 'O2': -0.58, 'N3': -0.29, 'C4': 0.1, 'N4': -0.6, 'C5': 0.31, 'C6': 0.31}, - 'DT': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.45, "C3'": 0.02, "O3'": -0.92, 'N1': -1.66, 'C2': 0.1, 'O2': -0.58, 'N3': 0.16, 'C4': 0.25, 'O4': -0.58, 'C5': 0.1, 'C6': 0.31, 'C7': 0.45}, - 'G': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.02, "O2'": -0.92, "C3'": 0.02, "O3'": -0.92, 'N9': -1.66, 'C8': 0.31, 'N7': -0.55, 'C5': 0.25, 'C6': 0.1, 'O6': -0.58, 'N1': -0.49, 'C2': 0.1, 'N2': -0.6, 'N3': -0.07, 'C4': -0.25}, - 'A': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.02, "O2'": -0.92, "C3'": 0.02, "O3'": -0.92, 'N9': -1.66, 'C8': 0.31, 'N7': -0.55, 'C5': 0.25, 'C6': 0.1, 'N6': -0.6, 'N1': -0.49, 'C2': 0.31, 'N2': -0.6, 'N3': -0.07, 'C4': -0.25}, - 'C': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.02, "O2'": -0.92, "C3'": 0.02, "O3'": -0.92, 'N1': -1.66, 'C2': 0.1, 'O2': -0.58, 'N3': -0.29, 'C4': 0.1, 'N4': -0.6, 'C5': 0.31, 'C6': 0.31}, - 'U': {'P': -0.94, 'O1P': -0.7, 'O2P': -0.22, "O5'": -0.5, "C5'": 0.45, "C4'": 0.02, "O4'": -1.14, "C1'": 0.02, "C2'": 0.02, "O2'": -0.92, "C3'": 0.02, "O3'": -0.92, 'N1': -1.66, 'C2': 0.1, 'O2': -0.58, 'N3': 0.16, 'C4': 0.25, 'O4': -0.58, 'C5': 0.1, 'C6': 0.31}, + "CA": {"CA": -1.0}, + "NAG": { + "C1": 0.02, + "C2": 0.02, + "C3": 0.02, + "C4": 0.02, + "C5": 0.02, + "C6": 0.31, + "C7": -0.61, + "C8": 0.62, + "O1": -0.92, + "O2": -0.92, + "O3": -0.92, + "O4": -0.92, + "O5": -1.14, + "O6": -0.99, + "O7": -0.58, + "N2": -0.49, + "H2": -0.25, + "HN2": -0.5, + }, + "NDG": { + "C1": 0.02, + "C2": 0.02, + "C3": 0.02, + "C4": 0.02, + "C5": 0.02, + "C6": 0.031, + "C7": -0.61, + "C8": 0.62, + "O1L": -0.9, + "O3": -0.92, + "O4": -0.92, + "O": -1.14, + "O6": -0.99, + "O7": -0.58, + "N2": -0.29, + }, + "BMA": { + "C1": 0.02, + "C2": 0.02, + "C3": 0.02, + "C4": 0.02, + "C5": 0.02, + "C6": 0.31, + "O1": -0.92, + "O2": -0.92, + "O3": -0.92, + "O4": -0.92, + "O5": -1.14, + "O6": -0.58, + }, + "MAN": { + "C1": 0.02, + "C2": 0.02, + "C3": 0.02, + "C4": 0.02, + "C5": 0.02, + "C6": 0.31, + "O1": -0.92, + "O2": -0.92, + "O3": -0.92, + "O4": -0.92, + "O5": -1.14, + "O6": -0.58, + }, + "GAL": { + "C1": 0.02, + "C2": 0.02, + "C3": 0.02, + "C4": 0.02, + "C5": 0.02, + "C6": 0.31, + "O1": -0.92, + "O2": -0.92, + "O3": -0.92, + "O4": -0.92, + "O5": -1.14, + "O6": -0.58, + }, + "NAN": { + "C1": -0.61, + "C2": 0.02, + "C3": 0.62, + "C4": 0.02, + "C5": 0.02, + "C6": 0.02, + "C7": 0.02, + "C8": 0.02, + "C9": 0.31, + "C10": -0.6, + "C11": 0.62, + "O1A": -0.2, + "O1B": -0.4, + "O2": -0.92, + "O4": -0.92, + "O6": -1.14, + "O7": -0.92, + "O8": -0.92, + "O9": -0.7, + "O10": -0.2, + "N5": -0.49, + "NH5": -0.5, + }, + "DG": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.45, + "C3'": 0.02, + "O3'": -0.92, + "N9": -1.66, + "C8": 0.31, + "N7": -0.55, + "C5": 0.25, + "C6": 0.1, + "O6": -0.58, + "N1": -0.49, + "C2": 0.1, + "N2": -0.6, + "N3": -0.07, + "C4": -0.25, + }, + "DA": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.45, + "C3'": 0.02, + "O3'": -0.92, + "N9": -1.66, + "C8": 0.31, + "N7": -0.55, + "C5": 0.25, + "C6": 0.1, + "N6": -0.6, + "N1": -0.49, + "C2": 0.31, + "N2": -0.6, + "N3": -0.07, + "C4": -0.25, + }, + "DC": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.45, + "C3'": 0.02, + "O3'": -0.92, + "N1": -1.66, + "C2": 0.1, + "O2": -0.58, + "N3": -0.29, + "C4": 0.1, + "N4": -0.6, + "C5": 0.31, + "C6": 0.31, + }, + "DT": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.45, + "C3'": 0.02, + "O3'": -0.92, + "N1": -1.66, + "C2": 0.1, + "O2": -0.58, + "N3": 0.16, + "C4": 0.25, + "O4": -0.58, + "C5": 0.1, + "C6": 0.31, + "C7": 0.45, + }, + "G": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.02, + "O2'": -0.92, + "C3'": 0.02, + "O3'": -0.92, + "N9": -1.66, + "C8": 0.31, + "N7": -0.55, + "C5": 0.25, + "C6": 0.1, + "O6": -0.58, + "N1": -0.49, + "C2": 0.1, + "N2": -0.6, + "N3": -0.07, + "C4": -0.25, + }, + "A": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.02, + "O2'": -0.92, + "C3'": 0.02, + "O3'": -0.92, + "N9": -1.66, + "C8": 0.31, + "N7": -0.55, + "C5": 0.25, + "C6": 0.1, + "N6": -0.6, + "N1": -0.49, + "C2": 0.31, + "N2": -0.6, + "N3": -0.07, + "C4": -0.25, + }, + "C": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.02, + "O2'": -0.92, + "C3'": 0.02, + "O3'": -0.92, + "N1": -1.66, + "C2": 0.1, + "O2": -0.58, + "N3": -0.29, + "C4": 0.1, + "N4": -0.6, + "C5": 0.31, + "C6": 0.31, + }, + "U": { + "P": -0.94, + "O1P": -0.7, + "O2P": -0.22, + "O5'": -0.5, + "C5'": 0.45, + "C4'": 0.02, + "O4'": -1.14, + "C1'": 0.02, + "C2'": 0.02, + "O2'": -0.92, + "C3'": 0.02, + "O3'": -0.92, + "N1": -1.66, + "C2": 0.1, + "O2": -0.58, + "N3": 0.16, + "C4": 0.25, + "O4": -0.58, + "C5": 0.1, + "C6": 0.31, + }, } # taken from biotite code: https://www.biotite-python.org/examples/gallery/structure/glycan_visualization.html # originally adapted from "Mol*" Software # The dictionary maps residue names of saccharides to their common names SACCHARIDE_NAMES = { - res_name: common_name for common_name, res_names in [ + res_name: common_name + for common_name, res_names in [ ("Glc", ["GLC", "BGC", "Z8T", "TRE", "MLR"]), ("Man", ["MAN", "BMA"]), ("Gal", ["GLA", "GAL", "GZL", "GXL", "GIV"]), @@ -1528,123 +3208,620 @@ "All": ("o", "purple"), "Tal": ("o", "lightsteelblue"), "Ido": ("o", "chocolate"), - "GlcNAc": ("s", "royalblue"), "ManNAc": ("s", "forestgreen"), "GalNAc": ("s", "gold"), "GulNAc": ("s", "darkorange"), "AllNAc": ("s", "purple"), "IdoNAc": ("s", "chocolate"), - "GlcN": ("1", "royalblue"), "ManN": ("1", "forestgreen"), "GalN": ("1", "gold"), - "GlcA": ("v", "royalblue"), "ManA": ("v", "forestgreen"), "GalA": ("v", "gold"), "GulA": ("v", "darkorange"), "TalA": ("v", "lightsteelblue"), "IdoA": ("v", "chocolate"), - "Qui": ("^", "royalblue"), "Rha": ("^", "forestgreen"), "6dGul": ("^", "darkorange"), "Fuc": ("^", "crimson"), - "QuiNAc": ("P", "royalblue"), "FucNAc": ("P", "crimson"), - "Oli": ("X", "royalblue"), "Tyv": ("X", "forestgreen"), "Abe": ("X", "darkorange"), "Par": ("X", "pink"), "Dig": ("X", "purple"), - "Ara": ("*", "forestgreen"), "Lyx": ("*", "gold"), "Xyl": ("*", "darkorange"), "Rib": ("*", "pink"), - "Kdn": ("D", "forestgreen"), "Neu5Ac": ("D", "mediumvioletred"), "Neu5Gc": ("D", "turquoise"), - "LDManHep": ("H", "forestgreen"), "Kdo": ("H", "gold"), "DDManHep": ("H", "pink"), "MurNAc": ("H", "purple"), "Mur": ("H", "chocolate"), - "Api": ("p", "royalblue"), "Fru": ("p", "forestgreen"), "Tag": ("p", "gold"), "Sor": ("p", "darkorange"), "Psi": ("p", "pink"), - # Default representation - None: ("h", "black") + None: ("h", "black"), } # Lipid names to match against for the `is_lipid` attribute lipid_names = ( - '23SM', 'CDL1', 'CDL2', 'ABLIPA', 'ABLIPB', 'ADR', 'ADRP', 'ALIN', 'ALINP', - 'APC', 'APPC', 'ARA', 'ARAN', 'ARANP', 'ARAP', 'ASM', 'BCLIPA', 'BCLIPB', 'BCLIPC', - 'BEH', 'BEHP', 'BNSM', 'BSM', 'C6DHPC', 'C7DHPC', 'CER160', 'CER180', 'CER181', - 'CER2', 'CER200', 'CER220', 'CER240', 'CER241', 'CER3E', 'CHAPS', 'CHAPSO', 'CHL1', - 'CHM1', 'CHNS', 'CHOA', 'CHSD', 'CHSP', 'CJLIPA', 'CPC', 'CTLIPA', 'CYFOS3', 'CYFOS4', - 'CYFOS5', 'CYFOS6', 'CYFOS7', 'CYSF', 'CYSG', 'CYSL', 'CYSP', 'DAPA', 'DAPA', 'DAPC', - 'DAPC', 'DAPE', 'DAPE', 'DAPG', 'DAPG', 'DAPS', 'DAPS', 'DBPA', 'DBPC', 'DBPE', - 'DBPG', 'DBPS', 'DBSM', 'DCPC', 'DDA', 'DDAO', 'DDAOP', 'DDAP', 'DDMG', 'DDOPC', - 'DDOPE', 'DDOPS', 'DDPC', 'DEPA', 'DEPC', 'DEPE', 'DEPG', 'DEPS', 'DFPA', 'DFPC', - 'DFPE', 'DFPG', 'DFPS', 'DGLA', 'DGLAP', 'DGPA', 'DGPA', 'DGPC', 'DGPC', 'DGPE', - 'DGPE', 'DGPG', 'DGPG', 'DGPS', 'DGPS', 'DHA', 'DHAP', 'DHPC', 'DHPCE', 'DIPA', - 'DIPA', 'DIPC', 'DIPE', 'DIPG', 'DIPS', 'DLIPC', 'DLIPE', 'DLIPI', 'DLPA', 'DLPA', - 'DLPC', 'DLPC', 'DLPE', 'DLPE', 'DLPG', 'DLPG', 'DLPS', 'DLPS', 'DMPA', 'DMPC', - 'DMPCE', 'DMPE', 'DMPEE', 'DMPG', 'DMPI', 'DMPI13', 'DMPI14', 'DMPI15', 'DMPI24', - 'DMPI25', 'DMPI2A', 'DMPI2B', 'DMPI2C', 'DMPI2D', 'DMPI33', 'DMPI34', 'DMPI35', - 'DMPS', 'DNPA', 'DNPA', 'DNPC', 'DNPC', 'DNPE', 'DNPE', 'DNPG', 'DNPG', 'DNPS', - 'DNPS', 'DOMG', 'DOPA', 'DOPA', 'DOPC', 'DOPC', 'DOPCE', 'DOPE', 'DOPE', 'DOPEE', - 'DOPG', 'DOPG', 'DOPP1', 'DOPP2', 'DOPP3', 'DOPS', 'DOPS', 'DPA', 'DPAP', 'DPC', - 'DPCE', 'DPP1', 'DPP2', 'DPPA', 'DPPA', 'DPPC', 'DPPC', 'DPPE', 'DPPE', 'DPPEE', - 'DPPG', 'DPPG', 'DPPGK', 'DPPI', 'DPPS', 'DPPS', 'DPSM', 'DPT', 'DPTP', 'DRPA', - 'DRPC', 'DRPE', 'DRPG', 'DRPS', 'DSPA', 'DSPC', 'DSPE', 'DSPG', 'DSPS', 'DTPA', - 'DTPA', 'DTPC', 'DTPE', 'DTPG', 'DTPS', 'DUPC', 'DUPE', 'DUPS', 'DVPA', 'DVPC', - 'DVPE', 'DVPG', 'DVPS', 'DXCE', 'DXPA', 'DXPA', 'DXPC', 'DXPC', 'DXPE', 'DXPE', - 'DXPG', 'DXPG', 'DXPS', 'DXPS', 'DXSM', 'DYPA', 'DYPA', 'DYPC', 'DYPC', 'DYPE', - 'DYPE', 'DYPG', 'DYPG', 'DYPS', 'DYPS', 'ECLIPA', 'ECLIPB', 'ECLIPC', 'EDA', 'EDAP', - 'EICO', 'EICOP', 'EPA', 'EPAP', 'ERG', 'ERU', 'ERUP', 'ETA', 'ETAP', 'ETE', 'ETEP', - 'FOIS11', 'FOIS9', 'FOS10', 'FOS12', 'FOS13', 'FOS14', 'FOS15', 'FOS16', 'GLA', - 'GLAP', 'GLYM', 'HPA', 'HPAP', 'HPLIPA', 'HPLIPB', 'HTA', 'HTAP', 'IPC', 'IPPC', - 'KPLIPA', 'KPLIPB', 'KPLIPC', 'LAPAO', 'LAPAOP', 'LAU', 'LAUP', 'LDAO', 'LDAOP', - 'LIGN', 'LIGNP', 'LILIPA', 'LIN', 'LINP', 'LLPA', 'LLPC', 'LLPE', 'LLPS', 'LMPG', - 'LNACL1', 'LNACL2', 'LNBCL1', 'LNBCL2', 'LNCCL1', 'LNCCL2', 'LNDCL1', 'LNDCL2', - 'LOACL1', 'LOACL2', 'LOCCL1', 'LOCCL2', 'LPC', 'LPC12', 'LPC14', 'LPPA', 'LPPC', - 'LPPC', 'LPPE', 'LPPG', 'LPPG', 'LPPS', 'LSM', 'LYSM', 'MCLIPA', 'MEA', 'MEAP', 'MYR', - 'MYRO', 'MYROP', 'MYRP', 'NER', 'NERP', 'NGLIPA', 'NGLIPB', 'NGLIPC', 'NSM', 'OLE', - 'OLEP', 'OPC', 'OSM', 'OSPE', 'OYPE', 'PADG', 'PAL', 'PALIPA', 'PALIPB', 'PALIPC', - 'PALIPD', 'PALIPE', 'PALO', 'PALOP', 'PALP', 'PAPA', 'PAPC', 'PAPE', 'PAPG', 'PAPI', - 'PAPS', 'PDOPC', 'PDOPE', 'PEPC', 'PGPA', 'PGPC', 'PGPE', 'PGPG', 'PGPS', 'PGSM', - 'PIDG', 'PIM1', 'PIM2', 'PIPA', 'PIPC', 'PIPE', 'PIPG', 'PIPI', 'PIPS', 'PLPA', - 'PLPC', 'PLPE', 'PLPG', 'PLPI', 'PLPI13', 'PLPI14', 'PLPI15', 'PLPI24', 'PLPI25', - 'PLPI2A', 'PLPI2B', 'PLPI2C', 'PLPI2D', 'PLPI33', 'PLPI34', 'PLPI35', 'PLPS', 'PMCL1', - 'PMCL2', 'PMPE', 'PMPG', 'PNCE', 'PNPI', 'PNPI13', 'PNPI14', 'PNPI15', 'PNPI24', - 'PNPI25', 'PNPI2A', 'PNPI2B', 'PNPI2C', 'PNPI2D', 'PNPI33', 'PNPI34', 'PNPI35', - 'PNSM', 'PODG', 'POP1', 'POP2', 'POP3', 'POPA', 'POPA', 'POPC', 'POPC', 'POPCE', - 'POPE', 'POPE', 'POPEE', 'POPG', 'POPG', 'POPI', 'POPI', 'POPI13', 'POPI14', 'POPI15', - 'POPI24', 'POPI25', 'POPI2A', 'POPI2B', 'POPI2C', 'POPI2D', 'POPI33', 'POPI34', - 'POPI35', 'POPP1', 'POPP2', 'POPP3', 'POPS', 'POPS', 'POSM', 'PPC', 'PPPE', 'PQPE', - 'PQPS', 'PRPA', 'PRPC', 'PRPE', 'PRPG', 'PRPS', 'PSM', 'PSPG', 'PUDG', 'PUPA', 'PUPC', - 'PUPE', 'PUPI', 'PUPS', 'PVCL2', 'PVDG', 'PVP1', 'PVP2', 'PVP3', 'PVPE', 'PVPG', - 'PVPI', 'PVSM', 'PYPE', 'PYPG', 'PYPI', 'PhPC', 'QMPE', 'SAPA', 'SAPC', 'SAPE', - 'SAPG', 'SAPI', 'SAPI13', 'SAPI14', 'SAPI15', 'SAPI24', 'SAPI25', 'SAPI2A', 'SAPI2B', - 'SAPI2C', 'SAPI2D', 'SAPI33', 'SAPI34', 'SAPI35', 'SAPS', 'SB3-10', 'SB3-12', - 'SB3-14', 'SDA', 'SDAP', 'SDPA', 'SDPC', 'SDPE', 'SDPG', 'SDPS', 'SDS', 'SELIPA', - 'SELIPB', 'SELIPC', 'SFLIPA', 'SITO', 'SLPA', 'SLPC', 'SLPE', 'SLPG', 'SLPS', 'SOPA', - 'SOPC', 'SOPE', 'SOPG', 'SOPS', 'SSM', 'STE', 'STEP', 'STIG', 'THA', 'THAP', 'THCHL', - 'THDPPC', 'TIPA', 'TLCL1', 'TLCL2', 'TMCL1', 'TMCL2', 'TOCL1', 'TOCL2', 'TPA', 'TPAP', - 'TPC', 'TPC', 'TPT', 'TPTP', 'TRI', 'TRIP', 'TRIPAO', 'TRPAOP', 'TSPC', 'TTA', 'TTAP', - 'TXCL1', 'TXCL2', 'TYCL1', 'TYCL2', 'UDAO', 'UDAOP', 'UFOS10', 'UPC', 'VCLIPA', - 'VCLIPB', 'VCLIPC', 'VCLIPD', 'VCLIPE', 'VPC', 'XNCE', 'XNSM', 'YOPA', 'YOPC', 'YOPE', - 'YOPS', 'YPLIPA', 'YPLIPB', 'bondedtypes' + "23SM", + "CDL1", + "CDL2", + "ABLIPA", + "ABLIPB", + "ADR", + "ADRP", + "ALIN", + "ALINP", + "APC", + "APPC", + "ARA", + "ARAN", + "ARANP", + "ARAP", + "ASM", + "BCLIPA", + "BCLIPB", + "BCLIPC", + "BEH", + "BEHP", + "BNSM", + "BSM", + "C6DHPC", + "C7DHPC", + "CER160", + "CER180", + "CER181", + "CER2", + "CER200", + "CER220", + "CER240", + "CER241", + "CER3E", + "CHAPS", + "CHAPSO", + "CHL1", + "CHM1", + "CHNS", + "CHOA", + "CHSD", + "CHSP", + "CJLIPA", + "CPC", + "CTLIPA", + "CYFOS3", + "CYFOS4", + "CYFOS5", + "CYFOS6", + "CYFOS7", + "CYSF", + "CYSG", + "CYSL", + "CYSP", + "DAPA", + "DAPA", + "DAPC", + "DAPC", + "DAPE", + "DAPE", + "DAPG", + "DAPG", + "DAPS", + "DAPS", + "DBPA", + "DBPC", + "DBPE", + "DBPG", + "DBPS", + "DBSM", + "DCPC", + "DDA", + "DDAO", + "DDAOP", + "DDAP", + "DDMG", + "DDOPC", + "DDOPE", + "DDOPS", + "DDPC", + "DEPA", + "DEPC", + "DEPE", + "DEPG", + "DEPS", + "DFPA", + "DFPC", + "DFPE", + "DFPG", + "DFPS", + "DGLA", + "DGLAP", + "DGPA", + "DGPA", + "DGPC", + "DGPC", + "DGPE", + "DGPE", + "DGPG", + "DGPG", + "DGPS", + "DGPS", + "DHA", + "DHAP", + "DHPC", + "DHPCE", + "DIPA", + "DIPA", + "DIPC", + "DIPE", + "DIPG", + "DIPS", + "DLIPC", + "DLIPE", + "DLIPI", + "DLPA", + "DLPA", + "DLPC", + "DLPC", + "DLPE", + "DLPE", + "DLPG", + "DLPG", + "DLPS", + "DLPS", + "DMPA", + "DMPC", + "DMPCE", + "DMPE", + "DMPEE", + "DMPG", + "DMPI", + "DMPI13", + "DMPI14", + "DMPI15", + "DMPI24", + "DMPI25", + "DMPI2A", + "DMPI2B", + "DMPI2C", + "DMPI2D", + "DMPI33", + "DMPI34", + "DMPI35", + "DMPS", + "DNPA", + "DNPA", + "DNPC", + "DNPC", + "DNPE", + "DNPE", + "DNPG", + "DNPG", + "DNPS", + "DNPS", + "DOMG", + "DOPA", + "DOPA", + "DOPC", + "DOPC", + "DOPCE", + "DOPE", + "DOPE", + "DOPEE", + "DOPG", + "DOPG", + "DOPP1", + "DOPP2", + "DOPP3", + "DOPS", + "DOPS", + "DPA", + "DPAP", + "DPC", + "DPCE", + "DPP1", + "DPP2", + "DPPA", + "DPPA", + "DPPC", + "DPPC", + "DPPE", + "DPPE", + "DPPEE", + "DPPG", + "DPPG", + "DPPGK", + "DPPI", + "DPPS", + "DPPS", + "DPSM", + "DPT", + "DPTP", + "DRPA", + "DRPC", + "DRPE", + "DRPG", + "DRPS", + "DSPA", + "DSPC", + "DSPE", + "DSPG", + "DSPS", + "DTPA", + "DTPA", + "DTPC", + "DTPE", + "DTPG", + "DTPS", + "DUPC", + "DUPE", + "DUPS", + "DVPA", + "DVPC", + "DVPE", + "DVPG", + "DVPS", + "DXCE", + "DXPA", + "DXPA", + "DXPC", + "DXPC", + "DXPE", + "DXPE", + "DXPG", + "DXPG", + "DXPS", + "DXPS", + "DXSM", + "DYPA", + "DYPA", + "DYPC", + "DYPC", + "DYPE", + "DYPE", + "DYPG", + "DYPG", + "DYPS", + "DYPS", + "ECLIPA", + "ECLIPB", + "ECLIPC", + "EDA", + "EDAP", + "EICO", + "EICOP", + "EPA", + "EPAP", + "ERG", + "ERU", + "ERUP", + "ETA", + "ETAP", + "ETE", + "ETEP", + "FOIS11", + "FOIS9", + "FOS10", + "FOS12", + "FOS13", + "FOS14", + "FOS15", + "FOS16", + "GLA", + "GLAP", + "GLYM", + "HPA", + "HPAP", + "HPLIPA", + "HPLIPB", + "HTA", + "HTAP", + "IPC", + "IPPC", + "KPLIPA", + "KPLIPB", + "KPLIPC", + "LAPAO", + "LAPAOP", + "LAU", + "LAUP", + "LDAO", + "LDAOP", + "LIGN", + "LIGNP", + "LILIPA", + "LIN", + "LINP", + "LLPA", + "LLPC", + "LLPE", + "LLPS", + "LMPG", + "LNACL1", + "LNACL2", + "LNBCL1", + "LNBCL2", + "LNCCL1", + "LNCCL2", + "LNDCL1", + "LNDCL2", + "LOACL1", + "LOACL2", + "LOCCL1", + "LOCCL2", + "LPC", + "LPC12", + "LPC14", + "LPPA", + "LPPC", + "LPPC", + "LPPE", + "LPPG", + "LPPG", + "LPPS", + "LSM", + "LYSM", + "MCLIPA", + "MEA", + "MEAP", + "MYR", + "MYRO", + "MYROP", + "MYRP", + "NER", + "NERP", + "NGLIPA", + "NGLIPB", + "NGLIPC", + "NSM", + "OLE", + "OLEP", + "OPC", + "OSM", + "OSPE", + "OYPE", + "PADG", + "PAL", + "PALIPA", + "PALIPB", + "PALIPC", + "PALIPD", + "PALIPE", + "PALO", + "PALOP", + "PALP", + "PAPA", + "PAPC", + "PAPE", + "PAPG", + "PAPI", + "PAPS", + "PDOPC", + "PDOPE", + "PEPC", + "PGPA", + "PGPC", + "PGPE", + "PGPG", + "PGPS", + "PGSM", + "PIDG", + "PIM1", + "PIM2", + "PIPA", + "PIPC", + "PIPE", + "PIPG", + "PIPI", + "PIPS", + "PLPA", + "PLPC", + "PLPE", + "PLPG", + "PLPI", + "PLPI13", + "PLPI14", + "PLPI15", + "PLPI24", + "PLPI25", + "PLPI2A", + "PLPI2B", + "PLPI2C", + "PLPI2D", + "PLPI33", + "PLPI34", + "PLPI35", + "PLPS", + "PMCL1", + "PMCL2", + "PMPE", + "PMPG", + "PNCE", + "PNPI", + "PNPI13", + "PNPI14", + "PNPI15", + "PNPI24", + "PNPI25", + "PNPI2A", + "PNPI2B", + "PNPI2C", + "PNPI2D", + "PNPI33", + "PNPI34", + "PNPI35", + "PNSM", + "PODG", + "POP1", + "POP2", + "POP3", + "POPA", + "POPA", + "POPC", + "POPC", + "POPCE", + "POPE", + "POPE", + "POPEE", + "POPG", + "POPG", + "POPI", + "POPI", + "POPI13", + "POPI14", + "POPI15", + "POPI24", + "POPI25", + "POPI2A", + "POPI2B", + "POPI2C", + "POPI2D", + "POPI33", + "POPI34", + "POPI35", + "POPP1", + "POPP2", + "POPP3", + "POPS", + "POPS", + "POSM", + "PPC", + "PPPE", + "PQPE", + "PQPS", + "PRPA", + "PRPC", + "PRPE", + "PRPG", + "PRPS", + "PSM", + "PSPG", + "PUDG", + "PUPA", + "PUPC", + "PUPE", + "PUPI", + "PUPS", + "PVCL2", + "PVDG", + "PVP1", + "PVP2", + "PVP3", + "PVPE", + "PVPG", + "PVPI", + "PVSM", + "PYPE", + "PYPG", + "PYPI", + "PhPC", + "QMPE", + "SAPA", + "SAPC", + "SAPE", + "SAPG", + "SAPI", + "SAPI13", + "SAPI14", + "SAPI15", + "SAPI24", + "SAPI25", + "SAPI2A", + "SAPI2B", + "SAPI2C", + "SAPI2D", + "SAPI33", + "SAPI34", + "SAPI35", + "SAPS", + "SB3-10", + "SB3-12", + "SB3-14", + "SDA", + "SDAP", + "SDPA", + "SDPC", + "SDPE", + "SDPG", + "SDPS", + "SDS", + "SELIPA", + "SELIPB", + "SELIPC", + "SFLIPA", + "SITO", + "SLPA", + "SLPC", + "SLPE", + "SLPG", + "SLPS", + "SOPA", + "SOPC", + "SOPE", + "SOPG", + "SOPS", + "SSM", + "STE", + "STEP", + "STIG", + "THA", + "THAP", + "THCHL", + "THDPPC", + "TIPA", + "TLCL1", + "TLCL2", + "TMCL1", + "TMCL2", + "TOCL1", + "TOCL2", + "TPA", + "TPAP", + "TPC", + "TPC", + "TPT", + "TPTP", + "TRI", + "TRIP", + "TRIPAO", + "TRPAOP", + "TSPC", + "TTA", + "TTAP", + "TXCL1", + "TXCL2", + "TYCL1", + "TYCL2", + "UDAO", + "UDAOP", + "UFOS10", + "UPC", + "VCLIPA", + "VCLIPB", + "VCLIPC", + "VCLIPD", + "VCLIPE", + "VPC", + "XNCE", + "XNSM", + "YOPA", + "YOPC", + "YOPE", + "YOPS", + "YPLIPA", + "YPLIPB", + "bondedtypes", ) diff --git a/molecularnodes/io/__init__.py b/molecularnodes/io/__init__.py index a73a6c1f..df75a4cb 100644 --- a/molecularnodes/io/__init__.py +++ b/molecularnodes/io/__init__.py @@ -1,12 +1,4 @@ -from .parse import ( - CIF, - BCIF, - PDB, - SDF, - CellPack, - StarFile, - MDAnalysisSession -) +from .parse import CIF, BCIF, PDB, SDF, CellPack, StarFile, MDAnalysisSession from .wwpdb import fetch from .local import load from .retrieve import download diff --git a/molecularnodes/io/cellpack.py b/molecularnodes/io/cellpack.py index 95156abf..6fe350d5 100644 --- a/molecularnodes/io/cellpack.py +++ b/molecularnodes/io/cellpack.py @@ -3,33 +3,29 @@ from . import parse bpy.types.Scene.mol_import_cell_pack_path = bpy.props.StringProperty( - name='File', - description='File to import (.cif, .bcif)', - subtype='FILE_PATH', - maxlen=0 + name="File", + description="File to import (.cif, .bcif)", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.mol_import_cell_pack_name = bpy.props.StringProperty( - name='Name', - description='Name of the created object.', - default='NewCellPackModel', - maxlen=0 + name="Name", + description="Name of the created object.", + default="NewCellPackModel", + maxlen=0, ) def load( file_path, - name='NewCellPackModel', + name="NewCellPackModel", node_setup=True, world_scale=0.01, fraction: float = 1, ): - ensemble = parse.CellPack(file_path) model = ensemble.create_model( - name=name, - node_setup=node_setup, - world_scale=world_scale, - fraction=fraction + name=name, node_setup=node_setup, world_scale=world_scale, fraction=fraction ) return model @@ -50,15 +46,15 @@ def execute(self, context): load( file_path=s.mol_import_cell_pack_path, name=s.mol_import_cell_pack_name, - node_setup=True + node_setup=True, ) return {"FINISHED"} def panel(layout, scene): - layout.label(text="Load CellPack Model", icon='FILE_TICK') + layout.label(text="Load CellPack Model", icon="FILE_TICK") layout.separator() row_import = layout.row() - row_import.prop(scene, 'mol_import_cell_pack_name') - layout.prop(scene, 'mol_import_cell_pack_path') - row_import.operator('mol.import_cell_pack') + row_import.prop(scene, "mol_import_cell_pack_name") + layout.prop(scene, "mol_import_cell_pack_path") + row_import.operator("mol.import_cell_pack") diff --git a/molecularnodes/io/density.py b/molecularnodes/io/density.py index cd471f69..dd18976a 100644 --- a/molecularnodes/io/density.py +++ b/molecularnodes/io/density.py @@ -4,56 +4,58 @@ bpy.types.Scene.MN_import_density_invert = bpy.props.BoolProperty( name="Invert Data", description="Invert the values in the map. Low becomes high, high becomes low.", - default=False + default=False, ) bpy.types.Scene.MN_import_density_center = bpy.props.BoolProperty( name="Center Density", description="Translate the density so that the center of the box is at the origin.", - default=False + default=False, ) bpy.types.Scene.MN_import_density = bpy.props.StringProperty( - name='File', - description='File path for the map file.', - subtype='FILE_PATH', - maxlen=0 + name="File", + description="File path for the map file.", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_density_name = bpy.props.StringProperty( - name='Name', - description='Name for the new density object.', - default='NewDensityObject', - maxlen=0 + name="Name", + description="Name for the new density object.", + default="NewDensityObject", + maxlen=0, ) bpy.types.Scene.MN_import_density_style = bpy.props.EnumProperty( - name='Style', + name="Style", items=( - ('density_surface', 'Surface', - 'A mesh surface based on the specified threshold', 0), - ('density_wire', 'Wire', 'A wire mesh surface based on the specified threshold', 1) - ) + ( + "density_surface", + "Surface", + "A mesh surface based on the specified threshold", + 0, + ), + ( + "density_wire", + "Wire", + "A wire mesh surface based on the specified threshold", + 1, + ), + ), ) def load( file_path: str, - name: str = 'NewDensity', + name: str = "NewDensity", invert: bool = False, setup_nodes: bool = True, - style: str = 'density_surface', + style: str = "density_surface", center: bool = False, - overwrite: bool = False + overwrite: bool = False, ): density = parse.MRC( - file_path=file_path, - center=center, - invert=invert, - overwrite=overwrite - ) - density.create_model( - name=name, - setup_nodes=setup_nodes, - style=style + file_path=file_path, center=center, invert=invert, overwrite=overwrite ) + density.create_model(name=name, setup_nodes=setup_nodes, style=style) return density @@ -75,20 +77,20 @@ def execute(self, context): invert=scene.MN_import_density_invert, setup_nodes=scene.MN_import_node_setup, style=scene.MN_import_density_style, - center=scene.MN_import_density_center + center=scene.MN_import_density_center, ) return {"FINISHED"} def panel(layout, scene): - layout.label(text='Load EM Map', icon='FILE_TICK') + layout.label(text="Load EM Map", icon="FILE_TICK") layout.separator() row = layout.row() - row.prop(scene, 'MN_import_density_name') - row.operator('mn.import_density') + row.prop(scene, "MN_import_density_name") + row.operator("mn.import_density") - layout.prop(scene, 'MN_import_density') + layout.prop(scene, "MN_import_density") layout.separator() col = layout.column() col.alignment = "LEFT" @@ -98,18 +100,18 @@ def panel(layout, scene): Please do not delete this file or the volume will not render.\ Move the original .map file to change this location.\ " - for line in label.strip().split(' '): + for line in label.strip().split(" "): col.label(text=line) layout.separator() layout.label(text="Options", icon="MODIFIER") row = layout.row() - row.prop(scene, 'MN_import_node_setup', text="") + row.prop(scene, "MN_import_node_setup", text="") col = row.column() col.prop(scene, "MN_import_density_style") col.enabled = scene.MN_import_node_setup grid = layout.grid_flow() - grid.prop(scene, 'MN_import_density_invert') - grid.prop(scene, 'MN_import_density_center') + grid.prop(scene, "MN_import_density_invert") + grid.prop(scene, "MN_import_density_center") diff --git a/molecularnodes/io/dna.py b/molecularnodes/io/dna.py index 4a9065fe..96c79810 100644 --- a/molecularnodes/io/dna.py +++ b/molecularnodes/io/dna.py @@ -1,27 +1,26 @@ -import numpy as np import bpy +import numpy as np + from .. import color -from ..blender import ( - obj, coll, nodes -) +from ..blender import coll, nodes, obj bpy.types.Scene.MN_import_oxdna_topology = bpy.props.StringProperty( - name='Toplogy', - description='File path for the topology to import (.top)', - subtype='FILE_PATH', - maxlen=0 + name="Toplogy", + description="File path for the topology to import (.top)", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_oxdna_trajectory = bpy.props.StringProperty( - name='Trajectory', - description='File path for the trajectory to import (.oxdna / .dat)', - subtype='FILE_PATH', - maxlen=0 + name="Trajectory", + description="File path for the trajectory to import (.oxdna / .dat)", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_oxdna_name = bpy.props.StringProperty( - name='Name', - description='Name of the created object.', - default='NewOrigami', - maxlen=0 + name="Name", + description="Name of the created object.", + default="NewOrigami", + maxlen=0, ) @@ -40,12 +39,7 @@ def base_to_int(bases: np.array) -> np.array: Array of corresponding integer values for the DNA bases. """ # Values for internal Molecular Nodes use. Defined in data.py - base_lookup = { - 'A': 30, - 'C': 31, - 'G': 32, - 'T': 33 - } + base_lookup = {"A": 30, "C": 31, "G": 32, "T": 33} ints = np.array([base_lookup.get(base, -1) for base in bases]) @@ -60,10 +54,10 @@ def is_new_topology(filepath): def read_topology_new(filepath): - with open(filepath, 'r') as file: + with open(filepath, "r") as file: contents = file.read() - lines = np.array(contents.split('\n')) + lines = np.array(contents.split("\n")) def read_seq_line(line): sequence = line.split(" ")[0] @@ -113,15 +107,15 @@ def read_topology_old(filepath): Returns ------- numpy.ndarray - The topology as a integer numpy array. Base assignment is (30, 31, 32, 33) where + The topology as a integer numpy array. Base assignment is (30, 31, 32, 33) where this corresponds to (A, C, G, T) for use inside of Molecular Nodes. """ - with open(filepath, 'r') as file: + with open(filepath, "r") as file: contents = file.read() - lines = np.array(contents.split('\n')) + lines = np.array(contents.split("\n")) # metadata = lines[0] # read the topology from the file sans the first metadata line @@ -131,7 +125,8 @@ def read_topology_old(filepath): # convert the columns to numeric array_int = np.zeros(array_str.shape, dtype=int) array_int[:, (0, 2, 3)] = array_str[:, (0, 2, 3)].astype( - int) # easy convert numeric columns to int + int + ) # easy convert numeric columns to int # convert bases (A, C, G, T) to (30, 31, 32, 33) array_int[:, 1] = base_to_int(array_str[:, 1]) @@ -142,8 +137,8 @@ def read_trajectory(filepath): """ Read an oxDNA trajectory file and return an array of frames. - Each frame becomes a 2D array in a stack. Each frame has 5 three-component vectors. - The vectors are: (position, base_vector, base_normal, veclocity, angular_velocity), + Each frame becomes a 2D array in a stack. Each frame has 5 three-component vectors. + The vectors are: (position, base_vector, base_normal, veclocity, angular_velocity), which totals 15 columns in the array. The (velocity, angular_velocity) are optional and can sometimes not appear in the trajectory. @@ -155,16 +150,16 @@ def read_trajectory(filepath): Returns ------- frames : ndarray - An array of frames, where each frame is a 2D array of positions + An array of frames, where each frame is a 2D array of positions """ # Open the file and read its contents - with open(filepath, 'r') as file: + with open(filepath, "r") as file: contents = file.read() # Split the contents into lines - lines = np.array(contents.split('\n')) - is_meta = np.char.find(lines, '=') > 0 + lines = np.array(contents.split("\n")) + is_meta = np.char.find(lines, "=") > 0 group_id = np.cumsum(np.append([True], np.diff(is_meta))) groups = np.unique(group_id) @@ -183,15 +178,14 @@ def read_trajectory(filepath): def set_attributes_to_dna_mol(mol, frame, scale_dna=0.1): - attributes = ('base_vector', 'base_normal', 'velocity', 'angular_velocity') + attributes = ("base_vector", "base_normal", "velocity", "angular_velocity") for i, att in enumerate(attributes): col_idx = np.array([3, 4, 5]) + i * 3 try: data = frame[:, col_idx] except IndexError as e: - print( - f"Unable to get {att} attribute from coordinates. Error: {e}") + print(f"Unable to get {att} attribute from coordinates. Error: {e}") continue if att != "angular_velocity": @@ -241,8 +235,7 @@ def toplogy_to_bond_idx_pairs(topology: np.ndarray): return np.sort(bond_idxs, axis=1) -def load(top, traj, name='oxDNA', setup_nodes=True, world_scale=0.01): - +def load(top, traj, name="oxDNA", setup_nodes=True, world_scale=0.01): # the scale of the oxDNA files seems to be based on nanometres rather than angstrongs # like most structural biology files, so currently adjusting the world_scale to # compensate @@ -264,14 +257,18 @@ def load(top, traj, name='oxDNA', setup_nodes=True, world_scale=0.01): name=name, collection=coll.mn(), vertices=trajectory[0][:, 0:3] * scale_dna, - edges=toplogy_to_bond_idx_pairs(topology) + edges=toplogy_to_bond_idx_pairs(topology), ) # adding additional toplogy information from the topology and frames objects - obj.set_attribute(mol, 'res_name', topology[:, 1], "INT") - obj.set_attribute(mol, 'chain_id', topology[:, 0], "INT") - obj.set_attribute(mol, 'Color', data=color.color_chains_equidistant( - topology[:, 0]), type='FLOAT_COLOR') + obj.set_attribute(mol, "res_name", topology[:, 1], "INT") + obj.set_attribute(mol, "chain_id", topology[:, 0], "INT") + obj.set_attribute( + mol, + "Color", + data=color.color_chains_equidistant(topology[:, 0]), + type="FLOAT_COLOR", + ) set_attributes_to_dna_mol(mol, trajectory[0], scale_dna=scale_dna) # if the 'frames' file only contained one timepoint, return the object without creating @@ -279,8 +276,7 @@ def load(top, traj, name='oxDNA', setup_nodes=True, world_scale=0.01): # object in place of the frames collection if n_frames == 1: if setup_nodes: - nodes.create_starting_node_tree( - mol, style="oxdna", set_color=False) + nodes.create_starting_node_tree(mol, style="oxdna", set_color=False) return mol, None # create a collection to store all of the frame objects that are part of the trajectory @@ -291,12 +287,14 @@ def load(top, traj, name='oxDNA', setup_nodes=True, world_scale=0.01): fill_n = int(np.ceil(np.log10(n_frames))) frame_name = f"{name}_frame_{str(i).zfill(fill_n)}" frame_mol = obj.create_object( - frame[:, 0:3] * scale_dna, name=frame_name, collection=collection) + frame[:, 0:3] * scale_dna, name=frame_name, collection=collection + ) set_attributes_to_dna_mol(frame_mol, frame, scale_dna) if setup_nodes: nodes.create_starting_node_tree( - mol, coll_frames=collection, style="oxdna", set_color=False) + mol, coll_frames=collection, style="oxdna", set_color=False + ) return mol, collection @@ -312,17 +310,17 @@ def execute(self, context): load( top=s.MN_import_oxdna_topology, traj=s.MN_import_oxdna_trajectory, - name=s.MN_import_oxdna_name + name=s.MN_import_oxdna_name, ) return {"FINISHED"} def panel(layout, scene): - layout.label(text="Load oxDNA File", icon='FILE_TICK') + layout.label(text="Load oxDNA File", icon="FILE_TICK") layout.separator() row = layout.row() - row.prop(scene, 'MN_import_oxdna_name') - row.operator('mn.import_oxdna') + row.prop(scene, "MN_import_oxdna_name") + row.operator("mn.import_oxdna") col = layout.column(align=True) - col.prop(scene, 'MN_import_oxdna_topology') - col.prop(scene, 'MN_import_oxdna_trajectory') + col.prop(scene, "MN_import_oxdna_topology") + col.prop(scene, "MN_import_oxdna_trajectory") diff --git a/molecularnodes/io/local.py b/molecularnodes/io/local.py index 4dd3c062..1fe6c499 100644 --- a/molecularnodes/io/local.py +++ b/molecularnodes/io/local.py @@ -1,47 +1,47 @@ -import bpy from pathlib import Path -import warnings + +import bpy + from . import parse bpy.types.Scene.MN_import_local_path = bpy.props.StringProperty( - name='File', - description='File path of the structure to open', - options={'TEXTEDIT_UPDATE'}, - subtype='FILE_PATH', - maxlen=0 + name="File", + description="File path of the structure to open", + options={"TEXTEDIT_UPDATE"}, + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_local_name = bpy.props.StringProperty( - name='Name', - description='Name of the molecule on import', - options={'TEXTEDIT_UPDATE'}, - default='NewMolecule', - maxlen=0 + name="Name", + description="Name of the molecule on import", + options={"TEXTEDIT_UPDATE"}, + default="NewMolecule", + maxlen=0, ) def load( file_path, name="Name", - centre='', + centre="", del_solvent=True, - style='spheres', - build_assembly=False + style="spheres", + build_assembly=False, ): from biotite import InvalidFileError suffix = Path(file_path).suffix parser = { - '.pdb': parse.PDB, - '.pdbx': parse.CIF, - '.cif': parse.CIF, - '.bcif': parse.BCIF, - '.mol': parse.SDF, - '.sdf': parse.SDF + ".pdb": parse.PDB, + ".pdbx": parse.CIF, + ".cif": parse.CIF, + ".bcif": parse.BCIF, + ".mol": parse.SDF, + ".sdf": parse.SDF, } if suffix not in parser: - raise ValueError( - f"Unable to open local file. Format '{suffix}' not supported.") + raise ValueError(f"Unable to open local file. Format '{suffix}' not supported.") try: molecule = parser[suffix](file_path) except InvalidFileError: @@ -52,10 +52,11 @@ def load( style=style, build_assembly=build_assembly, centre=centre, - del_solvent=del_solvent + del_solvent=del_solvent, ) return molecule + # operator that calls the function to import the structure from a local file @@ -73,7 +74,7 @@ def execute(self, context): if not scene.MN_import_node_setup: style = None - centre = '' + centre = "" if scene.MN_import_centre: centre = scene.MN_centre_type @@ -83,12 +84,12 @@ def execute(self, context): centre=centre, del_solvent=scene.MN_import_del_solvent, style=style, - build_assembly=scene.MN_import_build_assembly + build_assembly=scene.MN_import_build_assembly, ) # return the good news! bpy.context.view_layer.objects.active = mol.object - self.report({'INFO'}, message=f"Imported '{file_path}' as {mol.name}") + self.report({"INFO"}, message=f"Imported '{file_path}' as {mol.name}") return {"FINISHED"} def invoke(self, context, event): @@ -96,36 +97,35 @@ def invoke(self, context, event): def panel(layout, scene): - - layout.label(text='Load a Local File', icon='FILE_TICK') + layout.label(text="Load a Local File", icon="FILE_TICK") layout.separator() row_name = layout.row(align=False) - row_name.prop(scene, 'MN_import_local_name') - row_name.operator('mn.import_protein_local') + row_name.prop(scene, "MN_import_local_name") + row_name.operator("mn.import_protein_local") row_import = layout.row() - row_import.prop(scene, 'MN_import_local_path') + row_import.prop(scene, "MN_import_local_path") layout.separator() - layout.label(text='Options', icon='MODIFIER') + layout.label(text="Options", icon="MODIFIER") options = layout.column(align=True) row = options.row() - row.prop(scene, 'MN_import_node_setup', text='') + row.prop(scene, "MN_import_node_setup", text="") col = row.column() - col.prop(scene, 'MN_import_style') + col.prop(scene, "MN_import_style") col.enabled = scene.MN_import_node_setup row_centre = options.row() - row_centre.prop(scene, 'MN_import_centre', icon_value=0) + row_centre.prop(scene, "MN_import_centre", icon_value=0) # row_centre.prop() col_centre = row_centre.column() - col_centre.prop(scene, 'MN_centre_type', text='') + col_centre.prop(scene, "MN_centre_type", text="") col_centre.enabled = scene.MN_import_centre options.separator() grid = options.grid_flow() - grid.prop(scene, 'MN_import_build_assembly') - grid.prop(scene, 'MN_import_del_solvent', icon_value=0) + grid.prop(scene, "MN_import_build_assembly") + grid.prop(scene, "MN_import_del_solvent", icon_value=0) diff --git a/molecularnodes/io/md.py b/molecularnodes/io/md.py index c37cb1f1..65924956 100644 --- a/molecularnodes/io/md.py +++ b/molecularnodes/io/md.py @@ -22,55 +22,48 @@ class MockUniverse: else: HAS_mda = True -from .parse.mda import MDAnalysisSession from .. import pkg +from .parse.mda import MDAnalysisSession bpy.types.Scene.MN_import_md_topology = bpy.props.StringProperty( - name='Topology', - description='File path for the toplogy file for the trajectory', - subtype='FILE_PATH', - maxlen=0 + name="Topology", + description="File path for the toplogy file for the trajectory", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_md_trajectory = bpy.props.StringProperty( - name='Trajectory', - description='File path for the trajectory file for the trajectory', - subtype='FILE_PATH', - maxlen=0 + name="Trajectory", + description="File path for the trajectory file for the trajectory", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_md_name = bpy.props.StringProperty( - name='Name', - description='Name of the molecule on import', - default='NewTrajectory', - maxlen=0 + name="Name", + description="Name of the molecule on import", + default="NewTrajectory", + maxlen=0, ) bpy.types.Scene.MN_import_md_frame_start = bpy.props.IntProperty( - name="Start", - description="Frame start for importing MD trajectory", - default=0 + name="Start", description="Frame start for importing MD trajectory", default=0 ) bpy.types.Scene.MN_import_md_frame_step = bpy.props.IntProperty( - name="Step", - description="Frame step for importing MD trajectory", - default=1 + name="Step", description="Frame step for importing MD trajectory", default=1 ) bpy.types.Scene.MN_import_md_frame_stop = bpy.props.IntProperty( - name="Stop", - description="Frame stop for importing MD trajectory", - default=499 + name="Stop", description="Frame stop for importing MD trajectory", default=499 ) bpy.types.Scene.MN_md_selection = bpy.props.StringProperty( - name='Import Filter', + name="Import Filter", description='Custom MDAnalysis selection string, removing unselecte atoms. See: "https://docs.mdanalysis.org/stable/documentation_pages/selections.html"', - default='all' + default="all", ) bpy.types.Scene.MN_md_in_memory = bpy.props.BoolProperty( - name='In Memory', - description='True will load all of the requested frames into the scene and into memory. False will stream the trajectory from a live MDAnalysis session', - default=False + name="In Memory", + description="True will load all of the requested frames into the scene and into memory. False will stream the trajectory from a live MDAnalysis session", + default=False, ) bpy.types.Scene.list_index = bpy.props.IntProperty( - name="Index for trajectory selection list.", - default=0 + name="Index for trajectory selection list.", default=0 ) @@ -79,13 +72,13 @@ def load( traj, name="NewTrajectory", style="spheres", - selection: str = 'all', + selection: str = "all", start: int = 0, step: int = 1, stop: int = 499, subframes: int = 0, custom_selections: dict = {}, - in_memory: bool = False + in_memory: bool = False, ): universe = mda.Universe(top, traj) @@ -97,13 +90,14 @@ def load( extra_selections = {} for sel in custom_selections: extra_selections[sel.name] = sel.selection - mol = session.show(atoms=universe, - name=name, - style=style, - selection=selection, - custom_selections=extra_selections, - in_memory=in_memory - ) + mol = session.show( + atoms=universe, + name=name, + style=style, + selection=selection, + custom_selections=extra_selections, + in_memory=in_memory, + ) return mol, universe @@ -120,11 +114,13 @@ def poll(cls, context): def execute(self, context): scene = context.scene - if not pkg.is_current('MDAnalysis'): - self.report({'ERROR'}, - message="MDAnalysis is not installed. " - "Please install it to use this feature.") - return {'CANCELLED'} + if not pkg.is_current("MDAnalysis"): + self.report( + {"ERROR"}, + message="MDAnalysis is not installed. " + "Please install it to use this feature.", + ) + return {"CANCELLED"} top = scene.MN_import_md_topology traj = scene.MN_import_md_trajectory name = scene.MN_import_md_name @@ -139,17 +135,16 @@ def execute(self, context): stop=scene.MN_import_md_frame_stop, step=scene.MN_import_md_frame_step, custom_selections=scene.trajectory_selection_list, - in_memory=scene.MN_md_in_memory - + in_memory=scene.MN_md_in_memory, ) bpy.context.view_layer.objects.active = mol self.report( - {'INFO'}, + {"INFO"}, message=f"Imported '{top}' as {name} " - f"with {str(universe.trajectory.n_frames)} " - f"frames from '{traj}'." + f"with {str(universe.trajectory.n_frames)} " + f"frames from '{traj}'.", ) return {"FINISHED"} @@ -157,20 +152,20 @@ def execute(self, context): # UI + class TrajectorySelectionItem(bpy.types.PropertyGroup): """Group of properties for custom selections for MDAnalysis import.""" + bl_idname = "testing" name: bpy.props.StringProperty( - name="Attribute Name", - description="Attribute", - default="custom_selection" + name="Attribute Name", description="Attribute", default="custom_selection" ) selection: bpy.props.StringProperty( name="Selection String", description="String that provides a selection through MDAnalysis", - default="name CA" + default="name CA", ) @@ -184,15 +179,16 @@ class TrajectorySelectionItem(bpy.types.PropertyGroup): class MN_UL_TrajectorySelectionListUI(bpy.types.UIList): """UI List""" - def draw_item(self, context, layout, data, item, - icon, active_data, active_propname, index): + def draw_item( + self, context, layout, data, item, icon, active_data, active_propname, index + ): custom_icon = "VIS_SEL_11" - if self.layout_type in {'DEFAULT', 'COMPACT'}: + if self.layout_type in {"DEFAULT", "COMPACT"}: layout.label(text=item.name, icon=custom_icon) - elif self.layout_type in {'GRID'}: - layout.alignment = 'CENTER' + elif self.layout_type in {"GRID"}: + layout.alignment = "CENTER" layout.label(text="", icon=custom_icon) @@ -204,11 +200,10 @@ class TrajectorySelection_OT_NewItem(bpy.types.Operator): def execute(self, context): context.scene.trajectory_selection_list.add() - return {'FINISHED'} + return {"FINISHED"} class TrajectorySelection_OT_DeleteIem(bpy.types.Operator): - bl_idname = "trajectory_selection_list.delete_item" bl_label = "-" @@ -223,7 +218,7 @@ def execute(self, context): my_list.remove(index) context.scene.list_index = min(max(0, index - 1), len(my_list) - 1) - return {'FINISHED'} + return {"FINISHED"} def custom_selections(layout, scene): @@ -231,12 +226,18 @@ def custom_selections(layout, scene): row = layout.row(align=True) row = row.split(factor=0.9) - row.template_list('MN_UL_TrajectorySelectionListUI', 'A list', scene, - "trajectory_selection_list", scene, "list_index", rows=3) + row.template_list( + "MN_UL_TrajectorySelectionListUI", + "A list", + scene, + "trajectory_selection_list", + scene, + "list_index", + rows=3, + ) col = row.column() - col.operator('trajectory_selection_list.new_item', icon="ADD", text="") - col.operator('trajectory_selection_list.delete_item', - icon="REMOVE", text="") + col.operator("trajectory_selection_list.new_item", icon="ADD", text="") + col.operator("trajectory_selection_list.delete_item", icon="REMOVE", text="") if scene.list_index >= 0 and scene.trajectory_selection_list: item = scene.trajectory_selection_list[scene.list_index] @@ -248,29 +249,29 @@ def custom_selections(layout, scene): def panel(layout, scene): - layout.label(text="Load MD Trajectories", icon='FILE_TICK') + layout.label(text="Load MD Trajectories", icon="FILE_TICK") layout.separator() col = layout.column(align=True) row_import = col.row() - row_import.prop(scene, 'MN_import_md_name') - row_import.operator('mn.import_protein_md', text="Load") + row_import.prop(scene, "MN_import_md_name") + row_import.operator("mn.import_protein_md", text="Load") col.separator() - col.prop(scene, 'MN_import_md_topology') - col.prop(scene, 'MN_import_md_trajectory') + col.prop(scene, "MN_import_md_topology") + col.prop(scene, "MN_import_md_trajectory") layout.separator() layout.label(text="Options", icon="MODIFIER") row = layout.row() - row.prop(scene, 'MN_import_node_setup', text="") + row.prop(scene, "MN_import_node_setup", text="") col = row.column() col.prop(scene, "MN_import_style") col.enabled = scene.MN_import_node_setup - layout.prop(scene, 'MN_md_selection') + layout.prop(scene, "MN_md_selection") row_frame = layout.row(heading="Frames", align=True) - row_frame.prop(scene, 'MN_md_in_memory') + row_frame.prop(scene, "MN_md_in_memory") row = row_frame.row(align=True) - row.prop(scene, 'MN_import_md_frame_start') - row.prop(scene, 'MN_import_md_frame_step') - row.prop(scene, 'MN_import_md_frame_stop') + row.prop(scene, "MN_import_md_frame_start") + row.prop(scene, "MN_import_md_frame_step") + row.prop(scene, "MN_import_md_frame_stop") row.enabled = scene.MN_md_in_memory custom_selections(layout, scene) diff --git a/molecularnodes/io/parse/__init__.py b/molecularnodes/io/parse/__init__.py index e19866d0..c9e81a90 100644 --- a/molecularnodes/io/parse/__init__.py +++ b/molecularnodes/io/parse/__init__.py @@ -3,6 +3,7 @@ """ from .pdbx import CIF, BCIF + # from .bcif import BCIF # from .cif import CIF from .pdb import PDB diff --git a/molecularnodes/io/parse/assembly.py b/molecularnodes/io/parse/assembly.py index 6ae0b1a7..d0ef39c8 100644 --- a/molecularnodes/io/parse/assembly.py +++ b/molecularnodes/io/parse/assembly.py @@ -9,7 +9,6 @@ class AssemblyParser(metaclass=ABCMeta): - @abstractmethod def list_assemblies(self): """ @@ -29,21 +28,21 @@ def get_transformations(self, assembly_id): transformations on sets of chains for this assembly | chain IDs affected by the transformation | | 4x4 rotation, translation & scale matrix - | | | + | | | list[tuple[ndarray, ndarray]]] """ @abstractmethod def get_assemblies(self): """ - Parse all the transformations for each assembly, returning a dictionary of + Parse all the transformations for each assembly, returning a dictionary of key:value pairs of assembly_id:transformations. The transformations list comes from the `get_transformations(assembly_id)` method. Dictionary of all assemblies | Assembly ID | | List of transformations to create biological assembly. - | | | + | | | dict{'1', list[transformations]} """ diff --git a/molecularnodes/io/parse/bcif.py b/molecularnodes/io/parse/bcif.py index f99fe954..233b5c8e 100644 --- a/molecularnodes/io/parse/bcif.py +++ b/molecularnodes/io/parse/bcif.py @@ -39,11 +39,11 @@ def _atom_array_from_bcif(open_bcif): # check if a petworld CellPack model or not is_petworld = False - if 'PDB_model_num' in categories['pdbx_struct_assembly_gen'].field_names: - print('PetWorld!') + if "PDB_model_num" in categories["pdbx_struct_assembly_gen"].field_names: + print("PetWorld!") is_petworld = True - atom_site = categories['atom_site'] + atom_site = categories["atom_site"] n_atoms = atom_site.row_count # Initialise the atom array that will contain all of the data for the atoms @@ -51,32 +51,36 @@ def _atom_array_from_bcif(open_bcif): # we first pull out the coordinates as they are from 3 different fields, but all # other fields should be single self-contained fields mol = AtomArray(n_atoms) - coord_field_names = [f'Cartn_{axis}' for axis in 'xyz'] - mol.coord = np.hstack(list([ - np.array(atom_site[column]).reshape((n_atoms, 1)) for column in coord_field_names - ])) + coord_field_names = [f"Cartn_{axis}" for axis in "xyz"] + mol.coord = np.hstack( + list( + [ + np.array(atom_site[column]).reshape((n_atoms, 1)) + for column in coord_field_names + ] + ) + ) # the list of current atom_site_lookup = { - # have to make sure the chain_id ends up being the same as the space operatore - 'label_asym_id': 'chain_id', - 'label_atom_id': 'atom_name', - 'label_comp_id': 'res_name', - 'type_symbol': 'element', - 'label_seq_id': 'res_id', - 'B_iso_or_equiv': 'b_factor', - 'label_entity_id': 'entity_id', - 'pdbx_PDB_model_num': 'model_id', - 'pdbx_formal_charge': 'charge', - 'occupancy': 'occupany', - 'id': 'atom_id' + "label_asym_id": "chain_id", + "label_atom_id": "atom_name", + "label_comp_id": "res_name", + "type_symbol": "element", + "label_seq_id": "res_id", + "B_iso_or_equiv": "b_factor", + "label_entity_id": "entity_id", + "pdbx_PDB_model_num": "model_id", + "pdbx_formal_charge": "charge", + "occupancy": "occupany", + "id": "atom_id", } if is_petworld: # annotations[0][1] = 'pdbx_PDB_model_num' - atom_site_lookup.pop('label_asym_id') - atom_site_lookup['pdbx_PDB_model_num'] = 'chain_id' + atom_site_lookup.pop("label_asym_id") + atom_site_lookup["pdbx_PDB_model_num"] = "chain_id" for name in atom_site.field_names: # the coordinates have already been extracted so we can skip over those field names @@ -95,10 +99,8 @@ def _atom_array_from_bcif(open_bcif): # TODO this could be expanded to capture fields that are entirely '' and drop them # or fill them with 0s - if annotation_name == 'res_id' and data[0] == '': - data = np.array([ - 0 if x == '' else x for x in data - ]) + if annotation_name == "res_id" and data[0] == "": + data = np.array([0 if x == "" else x for x in data]) mol.set_annotation(annotation_name, data) @@ -115,42 +117,43 @@ def rotation_from_matrix(matrix): def _get_ops_from_bcif(open_bcif): is_petworld = False cats = open_bcif.data_blocks[0] - assembly_gen = cats['pdbx_struct_assembly_gen'] + assembly_gen = cats["pdbx_struct_assembly_gen"] gen_arr = np.column_stack( - list([assembly_gen[name] for name in assembly_gen.field_names])) + list([assembly_gen[name] for name in assembly_gen.field_names]) + ) dtype = [ - ('assembly_id', int), - ('chain_id', 'U10'), - ('trans_id', int), - ('rotation', float, 4), # quaternion form rotations - ('translation', float, 3) + ("assembly_id", int), + ("chain_id", "U10"), + ("trans_id", int), + ("rotation", float, 4), # quaternion form rotations + ("translation", float, 3), ] - ops = cats['pdbx_struct_oper_list'] + ops = cats["pdbx_struct_oper_list"] ok_names = [ - 'matrix[1][1]', - 'matrix[1][2]', - 'matrix[1][3]', - 'matrix[2][1]', - 'matrix[2][2]', - 'matrix[2][3]', - 'matrix[3][1]', - 'matrix[3][2]', - 'matrix[3][3]', - 'vector[1]', - 'vector[2]', - 'vector[3]' + "matrix[1][1]", + "matrix[1][2]", + "matrix[1][3]", + "matrix[2][1]", + "matrix[2][2]", + "matrix[2][3]", + "matrix[3][1]", + "matrix[3][2]", + "matrix[3][3]", + "vector[1]", + "vector[2]", + "vector[3]", ] # test if petworld - if 'PDB_model_num' in assembly_gen.field_names: - print('PetWorld!') + if "PDB_model_num" in assembly_gen.field_names: + print("PetWorld!") is_petworld = True - op_ids = np.array(ops['id']) - struct_ops = np.column_stack(list([ - np.array(ops[name]).reshape((ops.row_count, 1)) for name in ok_names - ])) - rotations = np.array(list([ - rotation_from_matrix(x[0:9].reshape((3, 3))) for x in struct_ops - ])) + op_ids = np.array(ops["id"]) + struct_ops = np.column_stack( + list([np.array(ops[name]).reshape((ops.row_count, 1)) for name in ok_names]) + ) + rotations = np.array( + list([rotation_from_matrix(x[0:9].reshape((3, 3))) for x in struct_ops]) + ) translations = struct_ops[:, 9:12] gen_list = [] @@ -160,31 +163,29 @@ def _get_ops_from_bcif(open_bcif): if "," in gen[1]: for gexpr in gen[1].split(","): if "-" in gexpr: - start, end = [int(x) - for x in gexpr.strip('()').split('-')] + start, end = [int(x) for x in gexpr.strip("()").split("-")] ids.extend((np.array(range(start, end + 1))).tolist()) else: - ids.append(int(gexpr.strip('()'))) + ids.append(int(gexpr.strip("()"))) else: - start, end = [int(x) for x in gen[1].strip('()').split('-')] + start, end = [int(x) for x in gen[1].strip("()").split("-")] ids.extend((np.array(range(start, end + 1))).tolist()) else: - ids = np.array([int(x) - for x in gen[1].strip("()").split(",")]).tolist() + ids = np.array([int(x) for x in gen[1].strip("()").split(",")]).tolist() real_ids = np.nonzero(np.in1d(op_ids, ids))[0] - chains = np.array(gen[2].strip(' ').split(',')) + chains = np.array(gen[2].strip(" ").split(",")) if is_petworld: # all chain of the model receive theses transformation chains = np.array([gen[3]]) arr = np.zeros(chains.size * len(real_ids), dtype=dtype) - arr['chain_id'] = np.tile(chains, len(real_ids)) + arr["chain_id"] = np.tile(chains, len(real_ids)) mask = np.repeat(np.array(real_ids), len(chains)) try: - arr['trans_id'] = gen[3] + arr["trans_id"] = gen[3] except IndexError: pass - arr['rotation'] = rotations[mask, :] - arr['translation'] = translations[mask, :] + arr["rotation"] = rotations[mask, :] + arr["translation"] = translations[mask, :] gen_list.append(arr) return np.concatenate(gen_list) @@ -240,8 +241,7 @@ def _decode(encoded_data: EncodedData) -> Union[np.ndarray, List[str]]: result = encoded_data["data"] for encoding in encoded_data["encoding"][::-1]: if encoding["kind"] in _decoders: - result = _decoders[encoding["kind"]]( - result, encoding) # type: ignore + result = _decoders[encoding["kind"]](result, encoding) # type: ignore else: raise ValueError(f"Unsupported encoding '{encoding['kind']}'") @@ -330,8 +330,7 @@ def _decode_interval_quantization( ) -> np.ndarray: delta = (encoding["max"] - encoding["min"]) / (encoding["numSteps"] - 1) return ( - np.array(data, dtype=_get_dtype( - encoding["srcType"])) * delta + encoding["min"] + np.array(data, dtype=_get_dtype(encoding["srcType"])) * delta + encoding["min"] ) @@ -406,16 +405,14 @@ def _decode_integer_packing( def _decode_string_array(data: bytes, encoding: StringArrayEncoding) -> List[str]: offsets = _decode( - EncodedData( - encoding=encoding["offsetEncoding"], data=encoding["offsets"]) + EncodedData(encoding=encoding["offsetEncoding"], data=encoding["offsets"]) ) - indices = _decode(EncodedData( - encoding=encoding["dataEncoding"], data=data)) + indices = _decode(EncodedData(encoding=encoding["dataEncoding"], data=data)) str = encoding["stringData"] strings = [""] for i in range(1, len(offsets)): - strings.append(str[offsets[i - 1]: offsets[i]]) # type: ignore + strings.append(str[offsets[i - 1] : offsets[i]]) # type: ignore return [strings[i + 1] for i in indices] # type: ignore @@ -555,8 +552,7 @@ def __init__(self, data_blocks: List[CifDataBlock]): def _decode_column(column: EncodedColumn) -> CifField: values = _decode(column["data"]) - value_kinds = _decode( - column["mask"]) if column["mask"] else None # type: ignore + value_kinds = _decode(column["mask"]) if column["mask"] else None # type: ignore # type: ignore return CifField(name=column["name"], values=values, value_kinds=value_kinds) @@ -570,8 +566,9 @@ def loads(data: Union[bytes, EncodedFile], lazy=True) -> CifFile: """ import msgpack - file: EncodedFile = data if isinstance( - data, dict) and "dataBlocks" in data else msgpack.loads(data) # type: ignore + file: EncodedFile = ( + data if isinstance(data, dict) and "dataBlocks" in data else msgpack.loads(data) + ) # type: ignore data_blocks = [ CifDataBlock( diff --git a/molecularnodes/io/parse/cellpack.py b/molecularnodes/io/parse/cellpack.py index f79fae53..02ec0d57 100644 --- a/molecularnodes/io/parse/cellpack.py +++ b/molecularnodes/io/parse/cellpack.py @@ -21,17 +21,14 @@ def __init__(self, file_path): self.chain_ids = self.data.chain_ids def create_model( - self, - name='CellPack', - node_setup: bool = True, - world_scale: float = 0.01, - fraction: float = 1.0 + self, + name="CellPack", + node_setup: bool = True, + world_scale: float = 0.01, + fraction: float = 1.0, ): - self.data_object = self._create_data_object(name=f'{name}') - self._create_object_instances( - name=name, - node_setup=node_setup - ) + self.data_object = self._create_data_object(name=f"{name}") + self._create_object_instances(name=name, node_setup=node_setup) self._setup_node_tree(fraction=fraction) @@ -54,9 +51,7 @@ def _read(self, file_path): return data def _create_object_instances( - self, - name: str = 'CellPack', - node_setup: bool = True + self, name: str = "CellPack", node_setup: bool = True ) -> bpy.types.Collection: collection = bl.coll.cellpack(name) @@ -69,56 +64,42 @@ def _create_object_instances( model, coll_none = molecule._create_model( array=chain_atoms, name=f"{str(i).rjust(4, '0')}_{chain}", - collection=collection + collection=collection, ) colors = np.tile(color.random_rgb(i), (len(chain_atoms), 1)) bl.obj.set_attribute( - model, - name="Color", - data=colors, - type="FLOAT_COLOR", - overwrite=True + model, name="Color", data=colors, type="FLOAT_COLOR", overwrite=True ) if node_setup: bl.nodes.create_starting_node_tree( - model, - name=f"MN_pack_instance_{name}", - set_color=False + model, name=f"MN_pack_instance_{name}", set_color=False ) self.data_collection = collection return collection - def _create_data_object(self, name='DataObject'): + def _create_data_object(self, name="DataObject"): data_object = bl.obj.create_data_object( - self.transformations, - name=name, - collection=bl.coll.mn() + self.transformations, name=name, collection=bl.coll.mn() ) - data_object['chain_ids'] = self.chain_ids + data_object["chain_ids"] = self.chain_ids return data_object - def _setup_node_tree( - self, - name='CellPack', - fraction=1.0, - as_points=False - ): + def _setup_node_tree(self, name="CellPack", fraction=1.0, as_points=False): mod = bl.nodes.get_mod(self.data_object) group = bl.nodes.new_group(name=f"MN_ensemble_{name}", fallback=False) mod.node_group = group - node_pack = bl.nodes.add_custom( - group, 'MN_pack_instances', location=[-100, 0]) - node_pack.inputs['Collection'].default_value = self.data_collection - node_pack.inputs['Fraction'].default_value = fraction - node_pack.inputs['As Points'].default_value = as_points + node_pack = bl.nodes.add_custom(group, "MN_pack_instances", location=[-100, 0]) + node_pack.inputs["Collection"].default_value = self.data_collection + node_pack.inputs["Fraction"].default_value = fraction + node_pack.inputs["As Points"].default_value = as_points link = group.links.new link(bl.nodes.get_input(group).outputs[0], node_pack.inputs[0]) diff --git a/molecularnodes/io/parse/cif.py b/molecularnodes/io/parse/cif.py index 1dafda0f..4f085c56 100644 --- a/molecularnodes/io/parse/cif.py +++ b/molecularnodes/io/parse/cif.py @@ -12,13 +12,13 @@ def __init__(self, file_path, extra_fields=None, sec_struct=True): self.file_path = file_path self.file = self._read() self.array = self._get_structure( - extra_fields=extra_fields, - sec_struct=sec_struct + extra_fields=extra_fields, sec_struct=sec_struct ) self.n_atoms = self.array.array_length() def _read(self): import biotite.structure.io.pdbx as pdbx + return pdbx.legacy.PDBxFile.read(self.file_path) def _get_structure(self, extra_fields: str = None, sec_struct=True, bonds=True): @@ -26,7 +26,7 @@ def _get_structure(self, extra_fields: str = None, sec_struct=True, bonds=True): import biotite.structure as struc from biotite import InvalidFileError - fields = ['b_factor', 'charge', 'occupancy', 'atom_id'] + fields = ["b_factor", "charge", "occupancy", "atom_id"] if extra_fields: [fields.append(x) for x in extra_fields] @@ -36,14 +36,14 @@ def _get_structure(self, extra_fields: str = None, sec_struct=True, bonds=True): array = pdbx.get_structure(self.file, extra_fields=extra_fields) try: array.set_annotation( - 'sec_struct', _get_secondary_structure(array, self.file)) + "sec_struct", _get_secondary_structure(array, self.file) + ) except KeyError: - warnings.warn('No secondary structure information.') + warnings.warn("No secondary structure information.") try: - array.set_annotation( - 'entity_id', _get_entity_id(array, self.file)) + array.set_annotation("entity_id", _get_entity_id(array, self.file)) except KeyError: - warnings.warn('Non entity_id information.') + warnings.warn("Non entity_id information.") except InvalidFileError: array = pdbx.get_component(self.file) @@ -52,25 +52,26 @@ def _get_structure(self, extra_fields: str = None, sec_struct=True, bonds=True): # on their residue names if not array.bonds and bonds: array.bonds = struc.bonds.connect_via_residue_names( - array, inter_residue=True) + array, inter_residue=True + ) return array def _entity_ids(self): - entities = self.file['entity'] + entities = self.file["entity"] if not entities: return None - return entities.get('pdbx_description', None) + return entities.get("pdbx_description", None) def _assemblies(self): return CIFAssemblyParser(self.file).get_assemblies() def _ss_label_to_int(label): - if 'HELX' in label: + if "HELX" in label: return 1 - elif 'STRN' in label: + elif "STRN" in label: return 2 else: return 3 @@ -108,23 +109,23 @@ def _get_secondary_structure(array, file): # alpha helices, but will sometimes contain also other secondary structure # information such as in AlphaFold predictions - conf = file.get_category('struct_conf') + conf = file.get_category("struct_conf") if not conf: raise KeyError - starts = conf['beg_auth_seq_id'].astype(int) - ends = conf['end_auth_seq_id'].astype(int) - chains = conf['end_auth_asym_id'].astype(str) - id_label = conf['id'].astype(str) + starts = conf["beg_auth_seq_id"].astype(int) + ends = conf["end_auth_seq_id"].astype(int) + chains = conf["end_auth_asym_id"].astype(str) + id_label = conf["id"].astype(str) # most files will have a separate category for the beta sheets # this can just be appended to the other start / end / id and be processed # as normal - sheet = file.get_category('struct_sheet_range') + sheet = file.get_category("struct_sheet_range") if sheet: - starts = np.append(starts, sheet['beg_auth_seq_id'].astype(int)) - ends = np.append(ends, sheet['end_auth_seq_id'].astype(int)) - chains = np.append(chains, sheet['end_auth_asym_id'].astype(str)) - id_label = np.append(id_label, np.repeat('STRN', len(sheet['id']))) + starts = np.append(starts, sheet["beg_auth_seq_id"].astype(int)) + ends = np.append(ends, sheet["end_auth_seq_id"].astype(int)) + chains = np.append(chains, sheet["end_auth_asym_id"].astype(str)) + id_label = np.append(id_label, np.repeat("STRN", len(sheet["id"]))) # convert the string labels to integer representations of the SS # AH: 1, BS: 2, LOOP: 3 @@ -137,12 +138,12 @@ def _get_secondary_structure(array, file): lookup = dict() for chain in np.unique(chains): arrays = [] - mask = (chain == chains) + mask = chain == chains start_sub = starts[mask] end_sub = ends[mask] id_sub = id_int[mask] - for (start, end, id) in zip(start_sub, end_sub, id_sub): + for start, end, id in zip(start_sub, end_sub, id_sub): idx = np.arange(start, end + 1, dtype=int) arr = np.zeros((len(idx), 2), dtype=int) arr[:, 0] = idx @@ -166,10 +167,10 @@ def _get_secondary_structure(array, file): def _get_entity_id(array, file): - entities = file.get_category('entity_poly') + entities = file.get_category("entity_poly") if not entities: raise KeyError - chain_ids = entities['pdbx_strand_id'] + chain_ids = entities["pdbx_strand_id"] # the chain_ids are an array of individual items np.array(['A,B', 'C', 'D,E,F']) # which need to be categorised as [1, 1, 2, 3, 3, 3] for their belonging to individual @@ -178,13 +179,14 @@ def _get_entity_id(array, file): chains = [] idx = [] for i, chain_str in enumerate(chain_ids): - for chain in chain_str.split(','): + for chain in chain_str.split(","): chains.append(chain) idx.append(i) entity_lookup = dict(zip(chains, idx)) - chain_id_int = np.array([entity_lookup.get(chain, -1) - for chain in array.chain_id], int) + chain_id_int = np.array( + [entity_lookup.get(chain, -1) for chain in array.chain_id], int + ) return chain_id_int @@ -196,6 +198,7 @@ def __init__(self, file_cif): def list_assemblies(self): import biotite.structure.io.pdbx as pdbx + return list(pdbx.list_assemblies(self._file).keys()) def get_transformations(self, assembly_id): @@ -278,10 +281,7 @@ def _get_transformations(struct_oper): for index, id in enumerate(struct_oper["id"]): rotation_matrix = np.array( [ - [ - float(struct_oper[f"matrix[{i}][{j}]"][index]) - for j in (1, 2, 3) - ] + [float(struct_oper[f"matrix[{i}][{j}]"][index]) for j in (1, 2, 3)] for i in (1, 2, 3) ] ) @@ -314,17 +314,14 @@ def _parse_operation_expression(expression): if "-" in gexpr: first, last = gexpr.split("-") operations.append( - [str(id) - for id in range(int(first), int(last) + 1)] + [str(id) for id in range(int(first), int(last) + 1)] ) else: operations.append([gexpr]) else: # Range of operation IDs, they must be integers first, last = expr.split("-") - operations.append( - [str(id) for id in range(int(first), int(last) + 1)] - ) + operations.append([str(id) for id in range(int(first), int(last) + 1)]) elif "," in expr: # List of operation IDs operations.append(expr.split(",")) diff --git a/molecularnodes/io/parse/density.py b/molecularnodes/io/parse/density.py index bc16cd16..c4881b77 100644 --- a/molecularnodes/io/parse/density.py +++ b/molecularnodes/io/parse/density.py @@ -1,6 +1,5 @@ from abc import ABCMeta import os -import numpy as np import bpy @@ -36,6 +35,6 @@ def path_to_vdb(self, file: str, center: False, invert: False): name = os.path.basename(file).split(".")[0] name += "_center" if center else "" name += "_invert" if invert else "" - file_name = name + '.vdb' + file_name = name + ".vdb" file_path = os.path.join(folder_path, file_name) return file_path diff --git a/molecularnodes/io/parse/ensemble.py b/molecularnodes/io/parse/ensemble.py index fb00e781..cdd00e58 100644 --- a/molecularnodes/io/parse/ensemble.py +++ b/molecularnodes/io/parse/ensemble.py @@ -23,7 +23,14 @@ def __init__(self, file_path): self.frames: bpy.types.Collection = None @classmethod - def create_model(cls, name: str = "NewEnsemble", node_setup: bool = True, world_scale: float = 0.01, fraction: float = 1.0, simplify=False): + def create_model( + cls, + name: str = "NewEnsemble", + node_setup: bool = True, + world_scale: float = 0.01, + fraction: float = 1.0, + simplify=False, + ): """ Create a 3D model in the of the ensemble. @@ -40,7 +47,7 @@ def create_model(cls, name: str = "NewEnsemble", node_setup: bool = True, world_ simplify : bool, optional Whether to isntance the given models or simplify them for debugging and performance. (default is False). - Creates a data object which stores all of the required instancing information. If + Creates a data object which stores all of the required instancing information. If there are molecules to be instanced, they are also created in their own data collection. Parameters: @@ -53,7 +60,7 @@ def create_model(cls, name: str = "NewEnsemble", node_setup: bool = True, world_ """ pass - def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: + def get_attribute(self, name="position", evaluate=False) -> np.ndarray | None: """ Get the value of an object for the data molecule. @@ -62,8 +69,8 @@ def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: name : str, optional The name of the attribute. Default is 'position'. evaluate : bool, optional - Whether to first evaluate all node trees before getting the requsted attribute. - False (default) will sample the underlying atomic geometry, while True will + Whether to first evaluate all node trees before getting the requsted attribute. + False (default) will sample the underlying atomic geometry, while True will sample the geometry that is created through the Geometry Nodes tree. Returns @@ -73,7 +80,7 @@ def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: """ if not self.object: warnings.warn( - 'No object yet created. Use `create_model()` to create a corresponding object.' + "No object yet created. Use `create_model()` to create a corresponding object." ) return None return bl.obj.get_attribute(self.object, name=name, evaluate=evaluate) diff --git a/molecularnodes/io/parse/mda.py b/molecularnodes/io/parse/mda.py index 0e684da0..98dece0a 100644 --- a/molecularnodes/io/parse/mda.py +++ b/molecularnodes/io/parse/mda.py @@ -28,17 +28,14 @@ class MockUniverse: from ... import data from ...pkg import start_logging -from ...blender import ( - coll, obj, nodes -) +from ...blender import coll, obj, nodes from ...utils import lerp class AtomGroupInBlender: - def __init__(self, - ag: mda.AtomGroup, - style: str = "vdw", - world_scale: float = 0.01): + def __init__( + self, ag: mda.AtomGroup, style: str = "vdw", world_scale: float = 0.01 + ): """ AtomGroup in Blender. It will be dynamically updated when the frame changes or @@ -144,8 +141,7 @@ def bonds(self) -> List[List[int]]: index_map = {index: i for i, index in enumerate(self.ag.indices)} - bonds = [[index_map[bond[0]], index_map[bond[1]]] - for bond in bond_indices] + bonds = [[index_map[bond[0]], index_map[bond[1]]] for bond in bond_indices] else: bonds = [] return bonds @@ -157,39 +153,54 @@ def elements(self) -> List[str]: except: try: elements = [ - x if x in data.elements.keys() else - mda.topology.guessers.guess_atom_element(x) for x in self.ag.atoms.names] + x + if x in data.elements.keys() + else mda.topology.guessers.guess_atom_element(x) + for x in self.ag.atoms.names + ] except: - elements = ['X'] * self.ag.n_atoms + elements = ["X"] * self.ag.n_atoms return elements @property def atomic_number(self) -> np.ndarray: return np.array( - [data.elements.get(element, - data.elements.get('X')) - .get('atomic_number') for element in self.elements] + [ + data.elements.get(element, data.elements.get("X")).get("atomic_number") + for element in self.elements + ] ) @property def vdw_radii(self) -> np.ndarray: # pm to Angstrom - return np.array( - [data.elements.get(element,{}).get( - 'vdw_radii',100) for element in self.elements]) * 0.01 * self.world_scale + return ( + np.array( + [ + data.elements.get(element, {}).get("vdw_radii", 100) + for element in self.elements + ] + ) + * 0.01 + * self.world_scale + ) @property def mass(self) -> np.ndarray: # units: daltons - try: + try: masses = np.array([x.mass for x in self.ag.atoms]) except mda.exceptions.NoDataError: masses = np.array( - [data.elements.get(element, - {'standard_mass': 0}) - .get('standard_mass') for element in self.elements]) - return masses + [ + data.elements.get(element, {"standard_mass": 0}).get( + "standard_mass" + ) + for element in self.elements + ] + ) + return masses @property def res_id(self) -> np.ndarray: @@ -202,9 +213,12 @@ def res_name(self) -> np.ndarray: @property def res_num(self) -> np.ndarray: return np.array( - [data.residues.get(res_name, - data.residues.get('UNK')) - .get('res_name_num') for res_name in self.res_name] + [ + data.residues.get(res_name, data.residues.get("UNK")).get( + "res_name_num" + ) + for res_name in self.res_name + ] ) @property @@ -227,8 +241,7 @@ def chain_ids(self) -> np.ndarray: @property def chain_id_num(self) -> np.ndarray: - chain_ids, chain_id_index = np.unique( - self.chain_id, return_inverse=True) + chain_ids, chain_id_index = np.unique(self.chain_id, return_inverse=True) return chain_id_index @property @@ -242,7 +255,8 @@ def atom_type_unique(self) -> np.ndarray: @property def atom_type_num(self) -> np.ndarray: atom_type_unique, atom_type_index = np.unique( - self.atom_type, return_inverse=True) + self.atom_type, return_inverse=True + ) return atom_type_index @property @@ -255,7 +269,9 @@ def atom_name(self) -> np.ndarray: @property def atom_name_num(self) -> np.ndarray: if hasattr(self.ag, "names"): - return np.array(list(map(lambda x: data.atom_names.get(x, -1), self.atom_name))) + return np.array( + list(map(lambda x: data.atom_names.get(x, -1), self.atom_name)) + ) else: return np.repeat(-1, self.ag.n_atoms) @@ -281,7 +297,9 @@ def is_alpha_carbon(self) -> np.ndarray: @property def is_solvent(self) -> np.ndarray: - return self.bool_selection(self.ag, "name OW or name HW1 or name HW2 or resname W or resname PW") + return self.bool_selection( + self.ag, "name OW or name HW1 or name HW2 or resname W or resname PW" + ) @property def _attributes_2_blender(self): @@ -332,7 +350,7 @@ def _attributes_2_blender(self): "atom_name": { "value": self.atom_name_num, "type": "INT", - "domain": "POINT" + "domain": "POINT", }, "is_backbone": { "value": self.is_backbone, @@ -476,47 +494,47 @@ def show( custom_selections: Dict[str, str] = {}, frame_mapping: np.ndarray = None, subframes: int = 0, - in_memory: bool = False + in_memory: bool = False, ): """ - Display an `MDAnalysis.Universe` or - `MDAnalysis.Atomgroup` in Blender. - - Parameters: - ---------- - atoms : MDAnalysis.Universe or MDAnalysis.Atomgroup - The universe to load into blender. - style : str, optional - The style to represent the atoms inside of Blender - (default: "vdw"). - selection : str, optional - The selection string for atom filtering - (default: "all"). - Uses MDAnalysis selection syntax. - name : str, optional - The name of the default atoms - (default: "atoms"). - custom_selections : dict, optional - A dictionary of custom selections for atom filtering with - {'name' : 'selection string'} - (default: {}). - Uses MDAnalysis selection syntax. - frame_mapping : np.ndarray, optional - A mapping from the frame indices in the Blender frame indices. - for example a frame_mapping of [0, 0, 1, 1, 2, 3] will map - the 1st frame (index 0) in the trajectory to the 1st and 2nd frames - in Blender and so on. - Note a subframes other than 1 will expand the frame_mapping from its - original length to (subframes + 1) * original length. - (default: None) which will map the frames in the trajectory - to the frames in Blender one-to-one. - subframes : int, optional - The number of subframes to interpolate between each frame. - (default: 0). - in_memory : bool, optional - Whether load the display in Blender by loading all the - frames as individual objects. - (default: False) + Display an `MDAnalysis.Universe` or + `MDAnalysis.Atomgroup` in Blender. + + Parameters: + ---------- + atoms : MDAnalysis.Universe or MDAnalysis.Atomgroup + The universe to load into blender. + style : str, optional + The style to represent the atoms inside of Blender + (default: "vdw"). + selection : str, optional + The selection string for atom filtering + (default: "all"). + Uses MDAnalysis selection syntax. + name : str, optional + The name of the default atoms + (default: "atoms"). + custom_selections : dict, optional + A dictionary of custom selections for atom filtering with + {'name' : 'selection string'} + (default: {}). + Uses MDAnalysis selection syntax. + frame_mapping : np.ndarray, optional + A mapping from the frame indices in the Blender frame indices. + for example a frame_mapping of [0, 0, 1, 1, 2, 3] will map + the 1st frame (index 0) in the trajectory to the 1st and 2nd frames + in Blender and so on. + Note a subframes other than 1 will expand the frame_mapping from its + original length to (subframes + 1) * original length. + (default: None) which will map the frames in the trajectory + to the frames in Blender one-to-one. + subframes : int, optional + The number of subframes to interpolate between each frame. + (default: 0). + in_memory : bool, optional + Whether load the display in Blender by loading all the + frames as individual objects. + (default: False) """ log = start_logging(logfile_name="mda") if in_memory: @@ -525,14 +543,14 @@ def show( style=style, selection=selection, name=name, - custom_selections=custom_selections + custom_selections=custom_selections, ) if frame_mapping is not None: - warnings.warn("Custom frame_mapping not supported" - "when in_memory is on.") + warnings.warn( + "Custom frame_mapping not supported" "when in_memory is on." + ) if subframes != 0: - warnings.warn("Custom subframes not supported" - "when in_memory is on.") + warnings.warn("Custom subframes not supported" "when in_memory is on.") log.info(f"{atoms} is loaded in memory.") return mol_object if isinstance(atoms, mda.Universe): @@ -542,8 +560,9 @@ def show( # if any frame_mapping is out of range, then raise an error if frame_mapping and (len(frame_mapping) > universe.trajectory.n_frames): - raise ValueError("one or more mapping values are" - "out of range for the trajectory") + raise ValueError( + "one or more mapping values are" "out of range for the trajectory" + ) mol_object = self._process_atomgroup( ag=atoms, @@ -551,7 +570,8 @@ def show( subframes=subframes, name=name, style=style, - return_object=True) + return_object=True, + ) # add the custom selections if they exist for sel_name, sel in custom_selections.items(): @@ -565,11 +585,10 @@ def show( subframes=subframes, name=sel_name, style=style, - return_object=False + return_object=False, ) except ValueError: - warnings.warn( - "Unable to add custom selection: {}".format(name)) + warnings.warn("Unable to add custom selection: {}".format(name)) bpy.context.view_layer.objects.active = mol_object log.info(f"{atoms} is loaded.") @@ -582,7 +601,7 @@ def in_memory( selection: str = "all", name: str = "atoms", custom_selections: Dict[str, str] = {}, - node_setup: bool = True + node_setup: bool = True, ): """ Display an `MDAnalysis.Universe` or @@ -741,9 +760,7 @@ def _process_atomgroup( Whether to return the blender object or not. Default: False """ ag_blender = AtomGroupInBlender( - ag=ag, - style=style, - world_scale=self.world_scale + ag=ag, style=style, world_scale=self.world_scale ) # create the initial model mol_object = obj.create_object( @@ -758,10 +775,10 @@ def _process_atomgroup( obj.set_attribute( mol_object, att_name, att["value"], att["type"], att["domain"] ) - mol_object['chain_ids'] = ag_blender.chain_ids - mol_object['atom_type_unique'] = ag_blender.atom_type_unique - mol_object.mn['subframes'] = subframes - mol_object.mn['molecule_type'] = 'md' + mol_object["chain_ids"] = ag_blender.chain_ids + mol_object["atom_type_unique"] = ag_blender.atom_type_unique + mol_object.mn["subframes"] = subframes + mol_object.mn["molecule_type"] = "md" # add the atomgroup to the session # the name of the atomgroup may be different from @@ -802,7 +819,7 @@ def _update_trajectory(self, frame): for rep_name in self.rep_names: universe = self.universe_reps[rep_name]["universe"] frame_mapping = self.universe_reps[rep_name]["frame_mapping"] - subframes = bpy.data.objects[rep_name].mn['subframes'] + subframes = bpy.data.objects[rep_name].mn["subframes"] if frame < 0: continue @@ -852,20 +869,17 @@ def _update_trajectory(self, frame): # then update as a new mol_object if isinstance(ag_rep.ag, mda.core.groups.UpdatingAtomGroup): mol_object.data.clear_geometry() - mol_object.data.from_pydata( - ag_rep.positions, - ag_rep.bonds, - faces=[]) + mol_object.data.from_pydata(ag_rep.positions, ag_rep.bonds, faces=[]) for att_name, att in ag_rep._attributes_2_blender.items(): obj.set_attribute( mol_object, att_name, att["value"], att["type"], att["domain"] ) - mol_object['chain_id'] = ag_rep.chain_ids - mol_object['atom_type_unique'] = ag_rep.atom_type_unique - mol_object.mn['subframes'] = subframes + mol_object["chain_id"] = ag_rep.chain_ids + mol_object["atom_type_unique"] = ag_rep.atom_type_unique + mol_object.mn["subframes"] = subframes else: # update the positions of the underlying vertices - obj.set_attribute(mol_object, 'position', locations) + obj.set_attribute(mol_object, "position", locations) @persistent def _update_trajectory_handler_wrapper(self): @@ -873,6 +887,7 @@ def _update_trajectory_handler_wrapper(self): A wrapper for the update_trajectory function because Blender requires the function to be taking one argument. """ + def update_trajectory_handler(scene): frame = scene.frame_current self._update_trajectory(frame) @@ -885,6 +900,7 @@ def _update_style_handler_wrapper(self): A wrapper for the update_style function because Blender requires the function to be taking one argument. """ + def update_style_handler(scene): self._remove_deleted_mol_objects() # TODO: check for topology changes @@ -906,15 +922,14 @@ def _remove_deleted_mol_objects(self): def _dump(self, blender_save_loc): """ - Dump the session as a pickle file + Dump the session as a pickle file """ log = start_logging(logfile_name="mda") # get blender_save_loc blender_save_loc = blender_save_loc.split(".blend")[0] with open(f"{blender_save_loc}.mda_session", "wb") as f: pickle.dump(self, f) - log.info("MDAnalysis session is dumped to {}". - format(blender_save_loc)) + log.info("MDAnalysis session is dumped to {}".format(blender_save_loc)) @classmethod def _rejuvenate(cls, mol_objects): @@ -937,8 +952,7 @@ def _rejuvenate(cls, mol_objects): bpy.app.handlers.depsgraph_update_pre.append( cls._update_style_handler_wrapper() ) - log.info("MDAnalysis session is loaded from {}". - format(blend_file_name)) + log.info("MDAnalysis session is loaded from {}".format(blend_file_name)) return cls @@ -968,8 +982,7 @@ def _rejuvenate_universe(scene): pass if len(mol_objects) > 0: - bpy.types.Scene.mda_session = MDAnalysisSession._rejuvenate( - mol_objects) + bpy.types.Scene.mda_session = MDAnalysisSession._rejuvenate(mol_objects) @persistent diff --git a/molecularnodes/io/parse/molecule.py b/molecularnodes/io/parse/molecule.py index c2b72e8f..85f49df4 100644 --- a/molecularnodes/io/parse/molecule.py +++ b/molecularnodes/io/parse/molecule.py @@ -1,12 +1,13 @@ -from abc import ABCMeta -from typing import Optional, Any -import warnings import time -import numpy as np +import warnings +from abc import ABCMeta +from typing import Optional + import bpy +import numpy as np from ... import blender as bl -from ... import utils, data, color +from ... import color, data, utils class Molecule(metaclass=ABCMeta): @@ -17,7 +18,7 @@ class Molecule(metaclass=ABCMeta): (the object). If multiple conformations are imported, then a `frames` collection is also instantiated. - The `get_attribute()` and `set_attribute()` methods access and set attributes on + The `get_attribute()` and `set_attribute()` methods access and set attributes on `object` that is in the Blender scene. Attributes @@ -57,7 +58,7 @@ def __init__(self): self.array: Optional[np.ndarray] = None def __len__(self): - if hasattr(self, 'object'): + if hasattr(self, "object"): if self.object: return len(self.object.data.vertices) if self.array: @@ -68,6 +69,7 @@ def __len__(self): @property def n_models(self): import biotite.structure as struc + if isinstance(self.array, struc.AtomArray): return 1 else: @@ -76,7 +78,7 @@ def n_models(self): @property def chain_ids(self) -> Optional[list]: if self.array: - if hasattr(self.array, 'chain_id'): + if hasattr(self.array, "chain_id"): return np.unique(self.array.chain_id).tolist() return None @@ -91,10 +93,10 @@ def name(self) -> Optional[str]: def set_attribute( self, data: np.ndarray, - name='NewAttribute', + name="NewAttribute", type=None, - domain='POINT', - overwrite=True + domain="POINT", + overwrite=True, ): """ Set an attribute for the molecule. @@ -107,30 +109,26 @@ def set_attribute( name : str, optional The name of the new attribute. Default is 'NewAttribute'. type : str, optional - If value is None (Default), the data type is inferred. The data type of the - attribute. Possbible values are ('FLOAT_VECTOR', 'FLOAT_COLOR", 'QUATERNION', + If value is None (Default), the data type is inferred. The data type of the + attribute. Possbible values are ('FLOAT_VECTOR', 'FLOAT_COLOR", 'QUATERNION', 'FLOAT', 'INT', 'BOOLEAN'). domain : str, optional - The domain of the attribute. Default is 'POINT'. Possible values are + The domain of the attribute. Default is 'POINT'. Possible values are currently ['POINT', 'EDGE', 'FACE', 'SPLINE'] overwrite : bool, optional - Whether to overwrite an existing attribute with the same name, or create a + Whether to overwrite an existing attribute with the same name, or create a new attribute with always a unique name. Default is True. """ if not self.object: warnings.warn( - f'No object yet created. Use `create_model()` to create a corresponding object.' + "No object yet created. Use `create_model()` to create a corresponding object." ) return None bl.obj.set_attribute( - self.object, - name=name, - data=data, - domain=domain, - overwrite=overwrite + self.object, name=name, data=data, domain=domain, overwrite=overwrite ) - def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: + def get_attribute(self, name="position", evaluate=False) -> np.ndarray | None: """ Get the value of an attribute for the associated object. @@ -139,8 +137,8 @@ def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: name : str, optional The name of the attribute. Default is 'position'. evaluate : bool, optional - Whether to first evaluate all node trees before getting the requsted attribute. - False (default) will sample the underlying atomic geometry, while True will + Whether to first evaluate all node trees before getting the requsted attribute. + False (default) will sample the underlying atomic geometry, while True will sample the geometry that is created through the Geometry Nodes tree. Returns @@ -150,7 +148,7 @@ def get_attribute(self, name='position', evaluate=False) -> np.ndarray | None: """ if not self.object: warnings.warn( - 'No object yet created. Use `create_model()` to create a corresponding object.' + "No object yet created. Use `create_model()` to create a corresponding object." ) return None return bl.obj.get_attribute(self.object, name=name, evaluate=evaluate) @@ -162,7 +160,7 @@ def list_attributes(self, evaluate=False) -> list | None: Parameters ---------- evaluate : bool, optional - Whether to first evaluate the modifiers on the object before listing the + Whether to first evaluate the modifiers on the object before listing the available attributes. Returns @@ -178,19 +176,19 @@ def list_attributes(self, evaluate=False) -> list | None: return list(self.object.data.attributes.keys()) - def centre(self, centre_type: str = 'centroid') -> np.ndarray: + def centre(self, centre_type: str = "centroid") -> np.ndarray: """ Calculate the centre of mass/geometry of the Molecule object :return: np.ndarray of shape (3,) user-defined centroid of all atoms in the Molecule object """ - positions = self.get_attribute(name='position') + positions = self.get_attribute(name="position") - if centre_type == 'centroid': + if centre_type == "centroid": return bl.obj.centre(positions) - elif centre_type == 'mass': - mass = self.get_attribute(name='mass') + elif centre_type == "mass": + mass = self.get_attribute(name="mass") return bl.obj.centre_weighted(positions, mass) else: raise ValueError( @@ -199,24 +197,24 @@ def centre(self, centre_type: str = 'centroid') -> np.ndarray: def create_model( self, - name: str = 'NewMolecule', - style: str = 'spheres', + name: str = "NewMolecule", + style: str = "spheres", selection: np.ndarray = None, build_assembly=False, - centre: str = '', + centre: str = "", del_solvent: bool = True, collection=None, verbose: bool = False, ) -> bpy.types.Object: """ - Create a 3D model of the molecule inside of Blender. + Create a 3D model of the molecule inside of Blender. Creates a 3D model with one vertex per atom, and one edge per bond. Each vertex is given attributes which correspond to the atomic data such as `atomic_number` for the element and `res_name` for the residue name that the atom is associated with. If multiple conformations of the structure are detected, the collection attribute - is also created which will store an object for each conformation, so that the + is also created which will store an object for each conformation, so that the object can interpolate between those conformations. Parameters @@ -232,7 +230,7 @@ def create_model( centre : str, optional Denote method used to determine center of structure. Default is '', resulting in no translational motion being removed. Accepted values - are `centroid` or `mass`. Any other value will result in default + are `centroid` or `mass`. Any other value will result in default behavior. del_solvent : bool, optional Whether to delete solvent molecules. Default is True. @@ -271,14 +269,14 @@ def create_model( ) try: - model['entity_ids'] = self.entity_ids + model["entity_ids"] = self.entity_ids except AttributeError: - model['entity_ids'] = None + model["entity_ids"] = None try: - model['biological_assemblies'] = self.assemblies() + model["biological_assemblies"] = self.assemblies() except InvalidFileError: - model['biological_assemblies'] = None + model["biological_assemblies"] = None pass if build_assembly and style: @@ -308,6 +306,7 @@ def assemblies(self, as_array=False): transformation matrices, or None if no assemblies are available. """ from biotite import InvalidFileError + try: assemblies_info = self._assemblies() except InvalidFileError: @@ -322,16 +321,18 @@ def __repr__(self) -> str: return f"" -def _create_model(array, - name=None, - centre='', - del_solvent=False, - style='spherers', - collection=None, - world_scale=0.01, - verbose=False - ) -> (bpy.types.Object, bpy.types.Collection): +def _create_model( + array, + name=None, + centre="", + del_solvent=False, + style="spherers", + collection=None, + world_scale=0.01, + verbose=False, +) -> (bpy.types.Object, bpy.types.Collection): import biotite.structure as struc + frames = None is_stack = isinstance(array, struc.AtomArrayStack) @@ -344,24 +345,25 @@ def _create_model(array, array = array[mask] try: - mass = np.array([ - data.elements.get(x, {}).get('standard_mass', 0.) - for x in np.char.title(array.element) - ]) - array.set_annotation('mass', mass) + mass = np.array( + [ + data.elements.get(x, {}).get("standard_mass", 0.0) + for x in np.char.title(array.element) + ] + ) + array.set_annotation("mass", mass) except AttributeError: pass def centre_array(atom_array, centre): - if centre == 'centroid': + if centre == "centroid": atom_array.coord -= bl.obj.centre(atom_array.coord) - elif centre == 'mass': + elif centre == "mass": atom_array.coord -= bl.obj.centre_weighted( - array=atom_array.coord, - weight=atom_array.mass + array=atom_array.coord, weight=atom_array.mass ) - if centre in ['mass', 'centroid']: + if centre in ["mass", "centroid"]: if is_stack: for atom_array in array: centre_array(atom_array, centre) @@ -383,14 +385,14 @@ def centre_array(atom_array, centre): bonds_array = array.bonds.as_array() bond_idx = bonds_array[:, [0, 1]] # the .copy(order = 'C') is to fix a weird ordering issue with the resulting array - bond_types = bonds_array[:, 2].copy(order='C') + bond_types = bonds_array[:, 2].copy(order="C") # creating the blender object and meshes and everything mol = bl.obj.create_object( name=name, collection=collection, vertices=array.coord * world_scale, - edges=bond_idx + edges=bond_idx, ) # Add information about the bond types to the model on the edge domain @@ -398,8 +400,9 @@ def centre_array(atom_array, centre): # 'AROMATIC_SINGLE' = 5, 'AROMATIC_DOUBLE' = 6, 'AROMATIC_TRIPLE' = 7 # https://www.biotite-python.org/apidoc/biotite.structure.BondType.html#biotite.structure.BondType if array.bonds: - bl.obj.set_attribute(mol, name='bond_type', data=bond_types, - type="INT", domain="EDGE") + bl.obj.set_attribute( + mol, name="bond_type", data=bond_types, type="INT", domain="EDGE" + ) # The attributes for the model are initially defined as single-use functions. This allows # for a loop that attempts to add each attibute by calling the function. Only during this @@ -411,10 +414,12 @@ def centre_array(atom_array, centre): # anybody might have. def att_atomic_number(): - atomic_number = np.array([ - data.elements.get(x, {'atomic_number': -1}).get("atomic_number") - for x in np.char.title(array.element) - ]) + atomic_number = np.array( + [ + data.elements.get(x, {"atomic_number": -1}).get("atomic_number") + for x in np.char.title(array.element) + ] + ) return atomic_number def att_atom_id(): @@ -432,24 +437,27 @@ def att_res_name(): res_nums = [] for name in res_names: - res_num = data.residues.get( - name, {'res_name_num': -1}).get('res_name_num') + res_num = data.residues.get(name, {"res_name_num": -1}).get("res_name_num") if res_num == 9999: - if res_names[counter - 1] != name or res_ids[counter] != res_ids[counter - 1]: + if ( + res_names[counter - 1] != name + or res_ids[counter] != res_ids[counter - 1] + ): id_counter += 1 unique_res_name = str(id_counter + 100) + "_" + str(name) other_res.append(unique_res_name) - num = np.where(np.isin(np.unique(other_res), unique_res_name))[ - 0][0] + 100 + num = ( + np.where(np.isin(np.unique(other_res), unique_res_name))[0][0] + 100 + ) res_nums.append(num) else: res_nums.append(res_num) counter += 1 - mol['ligands'] = np.unique(other_res) + mol["ligands"] = np.unique(other_res) return np.array(res_nums) def att_chain_id(): @@ -465,46 +473,58 @@ def att_occupancy(): return array.occupancy def att_vdw_radii(): - vdw_radii = np.array(list(map( - # divide by 100 to convert from picometres to angstroms which is - # what all of coordinates are in - lambda x: data.elements.get( - x, {}).get('vdw_radii', 100.) / 100, - np.char.title(array.element) - ))) + vdw_radii = np.array( + list( + map( + # divide by 100 to convert from picometres to angstroms which is + # what all of coordinates are in + lambda x: data.elements.get(x, {}).get("vdw_radii", 100.0) / 100, + np.char.title(array.element), + ) + ) + ) return vdw_radii * world_scale def att_mass(): return array.mass def att_atom_name(): - atom_name = np.array(list(map( - lambda x: data.atom_names.get(x, -1), - array.atom_name - ))) + atom_name = np.array( + list(map(lambda x: data.atom_names.get(x, -1), array.atom_name)) + ) return atom_name def att_lipophobicity(): - lipo = np.array(list(map( - lambda x, y: data.lipophobicity.get(x, {"0": 0}).get(y, 0), - array.res_name, array.atom_name - ))) + lipo = np.array( + list( + map( + lambda x, y: data.lipophobicity.get(x, {"0": 0}).get(y, 0), + array.res_name, + array.atom_name, + ) + ) + ) return lipo def att_charge(): - charge = np.array(list(map( - lambda x, y: data.atom_charge.get(x, {"0": 0}).get(y, 0), - array.res_name, array.atom_name - ))) + charge = np.array( + list( + map( + lambda x, y: data.atom_charge.get(x, {"0": 0}).get(y, 0), + array.res_name, + array.atom_name, + ) + ) + ) return charge def att_color(): return color.color_chains(att_atomic_number(), att_chain_id()) def att_is_alpha(): - return np.isin(array.atom_name, 'CA') + return np.isin(array.atom_name, "CA") def att_is_solvent(): return struc.filter_solvent(array) @@ -513,20 +533,34 @@ def att_is_backbone(): """ Get the atoms that appear in peptide backbone or nucleic acid phosphate backbones. Filter differs from the Biotite's `struc.filter_peptide_backbone()` in that this - includes the peptide backbone oxygen atom, which biotite excludes. Additionally - this selection also includes all of the atoms from the ribose in nucleic acids, + includes the peptide backbone oxygen atom, which biotite excludes. Additionally + this selection also includes all of the atoms from the ribose in nucleic acids, and the other phosphate oxygens. """ backbone_atom_names = [ - 'N', 'C', 'CA', 'O', # peptide backbone atoms - "P", "O5'", "C5'", "C4'", "C3'", "O3'", # 'continuous' nucleic backbone atoms - "O1P", "OP1", "O2P", "OP2", # alternative names for phosphate O's - "O4'", "C1'", "C2'", "O2'" # remaining ribose atoms + "N", + "C", + "CA", + "O", # peptide backbone atoms + "P", + "O5'", + "C5'", + "C4'", + "C3'", + "O3'", # 'continuous' nucleic backbone atoms + "O1P", + "OP1", + "O2P", + "OP2", # alternative names for phosphate O's + "O4'", + "C1'", + "C2'", + "O2'", # remaining ribose atoms ] is_backbone = np.logical_and( np.isin(array.atom_name, backbone_atom_names), - np.logical_not(struc.filter_solvent(array)) + np.logical_not(struc.filter_solvent(array)), ) return is_backbone @@ -552,50 +586,83 @@ def att_sec_struct(): # TODO add capcity for selection of particular attributes to include / not include to potentially # boost performance, unsure if actually a good idea of not. Need to do some testing. attributes = ( - {'name': 'res_id', 'value': att_res_id, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'res_name', 'value': att_res_name, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'atomic_number', 'value': att_atomic_number, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'b_factor', 'value': att_b_factor, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'occupancy', 'value': att_occupancy, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'vdw_radii', 'value': att_vdw_radii, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'mass', 'value': att_mass, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'chain_id', 'value': att_chain_id, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'entity_id', 'value': att_entity_id, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'atom_id', 'value': att_atom_id, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'atom_name', 'value': att_atom_name, - 'type': 'INT', 'domain': 'POINT'}, - {'name': 'lipophobicity', 'value': att_lipophobicity, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'charge', 'value': att_charge, - 'type': 'FLOAT', 'domain': 'POINT'}, - {'name': 'Color', 'value': att_color, - 'type': 'FLOAT_COLOR', 'domain': 'POINT'}, - {'name': 'is_backbone', 'value': att_is_backbone, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_alpha_carbon', 'value': att_is_alpha, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_solvent', 'value': att_is_solvent, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_nucleic', 'value': att_is_nucleic, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_peptide', 'value': att_is_peptide, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_hetero', 'value': att_is_hetero, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'is_carb', 'value': att_is_carb, - 'type': 'BOOLEAN', 'domain': 'POINT'}, - {'name': 'sec_struct', 'value': att_sec_struct, - 'type': 'INT', 'domain': 'POINT'} + {"name": "res_id", "value": att_res_id, "type": "INT", "domain": "POINT"}, + {"name": "res_name", "value": att_res_name, "type": "INT", "domain": "POINT"}, + { + "name": "atomic_number", + "value": att_atomic_number, + "type": "INT", + "domain": "POINT", + }, + {"name": "b_factor", "value": att_b_factor, "type": "FLOAT", "domain": "POINT"}, + { + "name": "occupancy", + "value": att_occupancy, + "type": "FLOAT", + "domain": "POINT", + }, + { + "name": "vdw_radii", + "value": att_vdw_radii, + "type": "FLOAT", + "domain": "POINT", + }, + {"name": "mass", "value": att_mass, "type": "FLOAT", "domain": "POINT"}, + {"name": "chain_id", "value": att_chain_id, "type": "INT", "domain": "POINT"}, + {"name": "entity_id", "value": att_entity_id, "type": "INT", "domain": "POINT"}, + {"name": "atom_id", "value": att_atom_id, "type": "INT", "domain": "POINT"}, + {"name": "atom_name", "value": att_atom_name, "type": "INT", "domain": "POINT"}, + { + "name": "lipophobicity", + "value": att_lipophobicity, + "type": "FLOAT", + "domain": "POINT", + }, + {"name": "charge", "value": att_charge, "type": "FLOAT", "domain": "POINT"}, + {"name": "Color", "value": att_color, "type": "FLOAT_COLOR", "domain": "POINT"}, + { + "name": "is_backbone", + "value": att_is_backbone, + "type": "BOOLEAN", + "domain": "POINT", + }, + { + "name": "is_alpha_carbon", + "value": att_is_alpha, + "type": "BOOLEAN", + "domain": "POINT", + }, + { + "name": "is_solvent", + "value": att_is_solvent, + "type": "BOOLEAN", + "domain": "POINT", + }, + { + "name": "is_nucleic", + "value": att_is_nucleic, + "type": "BOOLEAN", + "domain": "POINT", + }, + { + "name": "is_peptide", + "value": att_is_peptide, + "type": "BOOLEAN", + "domain": "POINT", + }, + { + "name": "is_hetero", + "value": att_is_hetero, + "type": "BOOLEAN", + "domain": "POINT", + }, + {"name": "is_carb", "value": att_is_carb, "type": "BOOLEAN", "domain": "POINT"}, + { + "name": "sec_struct", + "value": att_sec_struct, + "type": "INT", + "domain": "POINT", + }, ) # assign the attributes to the object @@ -603,26 +670,30 @@ def att_sec_struct(): if verbose: start = time.process_time() try: - bl.obj.set_attribute(mol, name=att['name'], data=att['value']( - ), type=att['type'], domain=att['domain']) + bl.obj.set_attribute( + mol, + name=att["name"], + data=att["value"](), + type=att["type"], + domain=att["domain"], + ) if verbose: - print( - f'Added {att["name"]} after {time.process_time() - start} s') + print(f'Added {att["name"]} after {time.process_time() - start} s') except: if verbose: warnings.warn(f"Unable to add attribute: {att['name']}") print( - f'Failed adding {att["name"]} after {time.process_time() - start} s') + f'Failed adding {att["name"]} after {time.process_time() - start} s' + ) coll_frames = None if frames: - coll_frames = bl.coll.frames(mol.name, parent=bl.coll.data()) for i, frame in enumerate(frames): frame = bl.obj.create_object( - name=mol.name + '_frame_' + str(i), + name=mol.name + "_frame_" + str(i), collection=coll_frames, - vertices=frame.coord * world_scale + vertices=frame.coord * world_scale, # vertices=frame.coord * world_scale - centroid ) # TODO if update_attribute @@ -634,9 +705,9 @@ def att_sec_struct(): # add custom properties to the actual blender object, such as number of chains, biological assemblies etc # currently biological assemblies can be problematic to holding off on doing that try: - mol['chain_ids'] = list(np.unique(array.chain_id)) + mol["chain_ids"] = list(np.unique(array.chain_id)) except AttributeError: - mol['chain_ids'] = None - warnings.warn('No chain information detected.') + mol["chain_ids"] = None + warnings.warn("No chain information detected.") return mol, coll_frames diff --git a/molecularnodes/io/parse/mrc.py b/molecularnodes/io/parse/mrc.py index aae97b42..ed1f2f7c 100644 --- a/molecularnodes/io/parse/mrc.py +++ b/molecularnodes/io/parse/mrc.py @@ -1,16 +1,17 @@ -from .density import Density +import os -from ...blender import coll, obj, nodes import bpy import numpy as np -import os + +from ...blender import coll, nodes, obj +from .density import Density class MRC(Density): """ A class for parsing EM density files in the format `.map` or `.map.gz`. - It utilises `mrcfile` for file parsing, which is then converted into `pyopevdb` grids, + It utilises `mrcfile` for file parsing, which is then converted into `pyopevdb` grids, that can be written as `.vdb` files and the imported into Blender as volumetric objects. """ @@ -19,17 +20,11 @@ def __init__(self, file_path, center=False, invert=False, overwrite=False): self.file_path = file_path self.grid = self.map_to_grid(self.file_path, center=center) self.file_vdb = self.map_to_vdb( - self.file_path, - center=center, - invert=invert, - overwrite=overwrite + self.file_path, center=center, invert=invert, overwrite=overwrite ) def create_model( - self, - name='NewDensity', - style='density_surface', - setup_nodes=True + self, name="NewDensity", style="density_surface", setup_nodes=True ) -> bpy.types.Object: """ Loads an MRC file into Blender as a volumetric object. @@ -51,7 +46,7 @@ def create_model( object = obj.import_vdb(self.file_vdb, collection=coll.mn()) object.location = (0, 0, 0) self.object = object - object.mn['molecule_type'] = 'density' + object.mn["molecule_type"] = "density" if name and name != "": # Rename object to specified name @@ -59,9 +54,7 @@ def create_model( if setup_nodes: nodes.create_starting_nodes_density( - object=object, - style=style, - threshold=self.threshold + object=object, style=style, threshold=self.threshold ) return object @@ -72,7 +65,7 @@ def map_to_vdb( invert: bool = False, world_scale=0.01, center: bool = False, - overwrite=False + overwrite=False, ) -> (str, float): """ Converts an MRC file to a .vdb file using pyopenvdb. @@ -104,25 +97,24 @@ def map_to_vdb( if os.path.exists(file_path) and not overwrite: # Also check that the file has the same invert and center settings grid = vdb.readAllGridMetadata(file_path)[0] - if 'MN_invert' in grid and grid['MN_invert'] == invert and 'MN_center' in grid and grid['MN_center'] == center: - self.threshold = grid['MN_initial_threshold'] + if ( + "MN_invert" in grid + and grid["MN_invert"] == invert + and "MN_center" in grid + and grid["MN_center"] == center + ): + self.threshold = grid["MN_initial_threshold"] return file_path print("Reading new file") # Read in the MRC file and convert it to a pyopenvdb grid - grid = self.map_to_grid( - file=file, - invert=invert, - center=center - ) + grid = self.map_to_grid(file=file, invert=invert, center=center) - grid.transform.scale( - np.array((1, 1, 1)) * world_scale * grid['MN_voxel_size'] - ) + grid.transform.scale(np.array((1, 1, 1)) * world_scale * grid["MN_voxel_size"]) if center: - offset = -np.array(grid['MN_box_size']) * 0.5 - offset *= grid['MN_voxel_size'] * world_scale + offset = -np.array(grid["MN_box_size"]) * 0.5 + offset *= grid["MN_voxel_size"] * world_scale print("transforming") grid.transform.translate(offset) @@ -130,9 +122,9 @@ def map_to_vdb( os.remove(file_path) # Write the grid to a .vdb file - print('writing new file') + print("writing new file") vdb.write(file_path, grids=[grid]) - self.threshold = grid['MN_initial_threshold'] + self.threshold = grid["MN_initial_threshold"] del grid # Return the path to the output file @@ -170,7 +162,7 @@ def map_to_grid(self, file: str, invert: bool = False, center: bool = False): if dataType == "float32" or dataType == "float64": grid = vdb.FloatGrid() elif dataType == "int8" or dataType == "int16" or dataType == "int32": - volume = volume.astype('int32') + volume = volume.astype("int32") grid = vdb.Int32Grid() elif dataType == "int64": grid = vdb.Int64Grid() @@ -185,24 +177,28 @@ def map_to_grid(self, file: str, invert: bool = False, center: bool = False): # The np.copy is needed to force numpy to actually rewrite the data in memory # since openvdb seems to read is straight from memory without checking the striding # The np.transpose is needed to convert the data from zyx to xyz - volume = np.copy(np.transpose(volume, (2, 1, 0)), order='C') + volume = np.copy(np.transpose(volume, (2, 1, 0)), order="C") try: grid.copyFromArray(volume.astype(float)) except Exception as e: print( - f"Grid data type '{volume.dtype}' is an unsupported type.\nError: {e}") + f"Grid data type '{volume.dtype}' is an unsupported type.\nError: {e}" + ) grid.gridClass = vdb.GridClass.FOG_VOLUME - grid.name = 'density' + grid.name = "density" # Set some metadata for the vdb file, so we can check if it's already been converted # correctly - grid['MN_invert'] = invert - grid['MN_initial_threshold'] = initial_threshold - grid['MN_center'] = center + grid["MN_invert"] = invert + grid["MN_initial_threshold"] = initial_threshold + grid["MN_center"] = center with mrcfile.open(file) as mrc: - grid['MN_voxel_size'] = float(mrc.voxel_size.x) - grid['MN_box_size'] = (int(mrc.header.nx), int( - mrc.header.ny), int(mrc.header.nz)) + grid["MN_voxel_size"] = float(mrc.voxel_size.x) + grid["MN_box_size"] = ( + int(mrc.header.nx), + int(mrc.header.ny), + int(mrc.header.nz), + ) return grid diff --git a/molecularnodes/io/parse/pdb.py b/molecularnodes/io/parse/pdb.py index bf0638c7..69c6b5cd 100644 --- a/molecularnodes/io/parse/pdb.py +++ b/molecularnodes/io/parse/pdb.py @@ -14,16 +14,18 @@ def __init__(self, file_path): def read(self): from biotite.structure.io import pdb + return pdb.PDBFile.read(self.file_path) def _get_structure(self): from biotite.structure.io import pdb from biotite.structure import BadStructureError + # TODO: implement entity ID, sec_struct for PDB files array = pdb.get_structure( pdb_file=self.file, - extra_fields=['b_factor', 'occupancy', 'charge', 'atom_id'], - include_bonds=True + extra_fields=["b_factor", "occupancy", "charge", "atom_id"], + include_bonds=True, ) try: @@ -31,9 +33,7 @@ def _get_structure(self): except BadStructureError: sec_struct = _comp_secondary_structure(array[0]) - array.set_annotation( - 'sec_struct', sec_struct - ) + array.set_annotation("sec_struct", sec_struct) return array @@ -45,22 +45,17 @@ def _get_sec_struct(file, array): import biotite.structure as struc lines = np.array(file.lines) - lines_helix = lines[np.char.startswith(lines, 'HELIX')] - lines_sheet = lines[np.char.startswith(lines, 'SHEET')] - if (len(lines_helix) == 0 and len(lines_sheet) == 0): - raise struc.BadStructureError( - 'No secondary structure information detected.' - ) + lines_helix = lines[np.char.startswith(lines, "HELIX")] + lines_sheet = lines[np.char.startswith(lines, "SHEET")] + if len(lines_helix) == 0 and len(lines_sheet) == 0: + raise struc.BadStructureError("No secondary structure information detected.") sec_struct = np.zeros(array.array_length(), int) helix_values = (22, 25, 34, 37, 20) sheet_values = (23, 26, 34, 37, 22) - values = ( - (lines_helix, 1, helix_values), - (lines_sheet, 2, sheet_values) - ) + values = ((lines_helix, 1, helix_values), (lines_sheet, 2, sheet_values)) def _get_mask(line, start1, end1, start2, end2, chainid): """ @@ -80,9 +75,8 @@ def _get_mask(line, start1, end1, start2, end2, chainid): # create a mask for the array based on these values mask = np.logical_and( - np.logical_and(array.chain_id == chain_id, - array.res_id >= start_num), - array.res_id <= end_num + np.logical_and(array.chain_id == chain_id, array.res_id >= start_num), + array.res_id <= end_num, ) return mask @@ -94,10 +88,7 @@ def _get_mask(line, start1, end1, start2, end2, chainid): # assign remaining AA atoms to 3 (loop), while all other remaining # atoms will be 0 (not relevant) - mask = np.logical_and( - sec_struct == 0, - struc.filter_canonical_amino_acids(array) - ) + mask = np.logical_and(sec_struct == 0, struc.filter_canonical_amino_acids(array)) sec_struct[mask] = 3 @@ -119,11 +110,10 @@ def _comp_secondary_structure(array): # TODO Port [PyDSSP](https://github.com/ShintaroMinami/PyDSSP) from biotite.structure import annotate_sse, spread_residue_wise - conv_sse_char_int = {'a': 1, 'b': 2, 'c': 3, '': 0} + conv_sse_char_int = {"a": 1, "b": 2, "c": 3, "": 0} char_sse = annotate_sse(array) - int_sse = np.array([conv_sse_char_int[char] - for char in char_sse], dtype=int) + int_sse = np.array([conv_sse_char_int[char] for char in char_sse], dtype=int) atom_sse = spread_residue_wise(array, int_sse) return atom_sse @@ -140,6 +130,7 @@ def list_assemblies(self): def get_transformations(self, assembly_id): import biotite + # Get lines containing transformations for assemblies remark_lines = self._file.get_remark(350) if remark_lines is None: @@ -163,31 +154,30 @@ def get_transformations(self, assembly_id): # the 'stop' is the end of REMARK 350 lines assembly_stop_i = len(remark_lines) if assembly_stop_i is None else i if assembly_start_i is None: - raise KeyError( - f"The assembly ID '{assembly_id}' is not found" - ) - assembly_lines = remark_lines[assembly_start_i: assembly_stop_i] + raise KeyError(f"The assembly ID '{assembly_id}' is not found") + assembly_lines = remark_lines[assembly_start_i:assembly_stop_i] # Get transformations for a sets of chains transformations = [] chain_set_start_indices = [ - i for i, line in enumerate(assembly_lines) + i + for i, line in enumerate(assembly_lines) if line.startswith("APPLY THE FOLLOWING TO CHAINS") ] # Add exclusive stop at end of records chain_set_start_indices.append(len(assembly_lines)) for i in range(len(chain_set_start_indices) - 1): start = chain_set_start_indices[i] - stop = chain_set_start_indices[i+1] + stop = chain_set_start_indices[i + 1] # Read affected chain IDs from the following line(s) affected_chain_ids = [] transform_start = None - for j, line in enumerate(assembly_lines[start: stop]): - if line.startswith("APPLY THE FOLLOWING TO CHAINS:") or \ - line.startswith(" AND CHAINS:"): + for j, line in enumerate(assembly_lines[start:stop]): + if line.startswith("APPLY THE FOLLOWING TO CHAINS:") or line.startswith( + " AND CHAINS:" + ): affected_chain_ids += [ - chain_id.strip() - for chain_id in line[30:].split(",") + chain_id.strip() for chain_id in line[30:].split(",") ] else: # Chain specification has finished @@ -200,8 +190,7 @@ def get_transformations(self, assembly_id): "No 'BIOMT' records found for chosen assembly" ) - matrices = _parse_transformations( - assembly_lines[transform_start: stop]) + matrices = _parse_transformations(assembly_lines[transform_start:stop]) for matrix in matrices: transformations.append((affected_chain_ids, matrix.tolist())) @@ -223,10 +212,10 @@ def _parse_transformations(lines): Return as array of matrices and vectors respectively """ import biotite + # Each transformation requires 3 lines for the (x,y,z) components if len(lines) % 3 != 0: - raise biotite.InvalidFileError( - "Invalid number of transformation vectors") + raise biotite.InvalidFileError("Invalid number of transformation vectors") n_transformations = len(lines) // 3 matrices = np.tile(np.identity(4), (n_transformations, 1, 1)) diff --git a/molecularnodes/io/parse/pdbx.py b/molecularnodes/io/parse/pdbx.py index 49990d73..94a64b31 100644 --- a/molecularnodes/io/parse/pdbx.py +++ b/molecularnodes/io/parse/pdbx.py @@ -12,10 +12,10 @@ def __init__(self, file_path): @property def entity_ids(self): - return self.file.block.get('entity').get('pdbx_description').as_array().tolist() + return self.file.block.get("entity").get("pdbx_description").as_array().tolist() def _get_entity_id(self, array, file): - chain_ids = file.block['entity_poly']['pdbx_strand_id'].as_array() + chain_ids = file.block["entity_poly"]["pdbx_strand_id"].as_array() # the chain_ids are an array of individual items np.array(['A,B', 'C', 'D,E,F']) # which need to be categorised as [1, 1, 2, 3, 3, 3] for their belonging to individual @@ -24,37 +24,38 @@ def _get_entity_id(self, array, file): chains = [] idx = [] for i, chain_str in enumerate(chain_ids): - for chain in chain_str.split(','): + for chain in chain_str.split(","): chains.append(chain) idx.append(i) entity_lookup = dict(zip(chains, idx)) - chain_id_int = np.array([entity_lookup.get(chain, -1) - for chain in array.chain_id], int) + chain_id_int = np.array( + [entity_lookup.get(chain, -1) for chain in array.chain_id], int + ) return chain_id_int - def get_structure(self, extra_fields=['b_factor', 'occupancy', 'atom_id'], bonds=True): + def get_structure( + self, extra_fields=["b_factor", "occupancy", "atom_id"], bonds=True + ): import biotite.structure.io.pdbx as pdbx import biotite.structure as struc array = pdbx.get_structure(self.file, extra_fields=extra_fields) try: array.set_annotation( - 'sec_struct', self._get_secondary_structure( - array=array, file=self.file) + "sec_struct", self._get_secondary_structure(array=array, file=self.file) ) except KeyError: - warnings.warn('No secondary structure information.') + warnings.warn("No secondary structure information.") try: - array.set_annotation( - 'entity_id', self._get_entity_id(array, self.file) - ) + array.set_annotation("entity_id", self._get_entity_id(array, self.file)) except KeyError: - warnings.warn('No entity ID information') + warnings.warn("No entity ID information") if not array.bonds and bonds: array.bonds = struc.bonds.connect_via_residue_names( - array, inter_residue=True) + array, inter_residue=True + ) return array @@ -104,24 +105,21 @@ def _assemblies(self): def _extract_matrices(self, category): matrix_columns = [ - 'matrix[1][1]', - 'matrix[1][2]', - 'matrix[1][3]', - 'vector[1]', - 'matrix[2][1]', - 'matrix[2][2]', - 'matrix[2][3]', - 'vector[2]', - 'matrix[3][1]', - 'matrix[3][2]', - 'matrix[3][3]', - 'vector[3]' + "matrix[1][1]", + "matrix[1][2]", + "matrix[1][3]", + "vector[1]", + "matrix[2][1]", + "matrix[2][2]", + "matrix[2][3]", + "vector[2]", + "matrix[3][1]", + "matrix[3][2]", + "matrix[3][3]", + "vector[3]", ] - columns = [ - category[name].as_array().astype(float) for - name in matrix_columns - ] + columns = [category[name].as_array().astype(float) for name in matrix_columns] matrices = np.empty((len(columns[0]), 4, 4), float) col_mask = np.tile((0, 1, 2, 3), 3) @@ -163,12 +161,12 @@ def _get_secondary_structure(self, file, array): # alpha helices, but will sometimes contain also other secondary structure # information such as in AlphaFold predictions - conf = file.block.get('struct_conf') + conf = file.block.get("struct_conf") if conf: - starts = conf['beg_auth_seq_id'].as_array().astype(int) - ends = conf['end_auth_seq_id'].as_array().astype(int) - chains = conf['end_auth_asym_id'].as_array().astype(str) - id_label = conf['id'].as_array().astype(str) + starts = conf["beg_auth_seq_id"].as_array().astype(int) + ends = conf["end_auth_seq_id"].as_array().astype(int) + chains = conf["end_auth_asym_id"].as_array().astype(str) + id_label = conf["id"].as_array().astype(str) else: starts = np.empty(0, dtype=int) ends = np.empty(0, dtype=int) @@ -178,15 +176,12 @@ def _get_secondary_structure(self, file, array): # most files will have a separate category for the beta sheets # this can just be appended to the other start / end / id and be processed # as normalquit - sheet = file.block.get('struct_sheet_range') + sheet = file.block.get("struct_sheet_range") if sheet: - starts = np.append( - starts, sheet['beg_auth_seq_id'].as_array().astype(int)) - ends = np.append( - ends, sheet['end_auth_seq_id'].as_array().astype(int)) - chains = np.append( - chains, sheet['end_auth_asym_id'].as_array().astype(str)) - id_label = np.append(id_label, np.repeat('STRN', len(sheet['id']))) + starts = np.append(starts, sheet["beg_auth_seq_id"].as_array().astype(int)) + ends = np.append(ends, sheet["end_auth_seq_id"].as_array().astype(int)) + chains = np.append(chains, sheet["end_auth_asym_id"].as_array().astype(str)) + id_label = np.append(id_label, np.repeat("STRN", len(sheet["id"]))) if not conf and not sheet: raise KeyError @@ -202,12 +197,12 @@ def _get_secondary_structure(self, file, array): lookup = dict() for chain in np.unique(chains): arrays = [] - mask = (chain == chains) + mask = chain == chains start_sub = starts[mask] end_sub = ends[mask] id_sub = id_int[mask] - for (start, end, id) in zip(start_sub, end_sub, id_sub): + for start, end, id in zip(start_sub, end_sub, id_sub): idx = np.arange(start, end + 1, dtype=int) arr = np.zeros((len(idx), 2), dtype=int) arr[:, 0] = idx @@ -234,9 +229,9 @@ def _parse_opers(oper): # we want the example '1,3,(5-8)' to expand to (1, 3, 5, 6, 7, 8). op_ids = list() - for group in oper.strip(')').split('('): + for group in oper.strip(")").split("("): if "," in group: - for i in group.split(','): + for i in group.split(","): op_ids.append() for group in oper.split(","): @@ -244,7 +239,7 @@ def _parse_opers(oper): op_ids.append(str(group)) continue - start, stop = [int(x) for x in group.strip("()").split('-')] + start, stop = [int(x) for x in group.strip("()").split("-")] for i in range(start, stop + 1): op_ids.append(str(i)) @@ -252,9 +247,9 @@ def _parse_opers(oper): def _ss_label_to_int(label): - if 'HELX' in label: + if "HELX" in label: return 1 - elif 'STRN' in label: + elif "STRN" in label: return 2 else: return 3 @@ -269,6 +264,7 @@ def __init__(self, file_path): def _read(self, file_path): import biotite.structure.io.pdbx as pdbx + return pdbx.CIFFile.read(file_path) @@ -281,6 +277,7 @@ def __init__(self, file_path): def _read(self, file_path): import biotite.structure.io.pdbx as pdbx + return pdbx.BinaryCIFFile.read(file_path) @@ -292,6 +289,7 @@ def __init__(self, file_cif): def list_assemblies(self): import biotite.structure.io.pdbx as pdbx + return list(pdbx.list_assemblies(self._file).keys()) def get_transformations(self, assembly_id): @@ -339,24 +337,21 @@ def get_assemblies(self): def _extract_matrices(category, scale=True): matrix_columns = [ - 'matrix[1][1]', - 'matrix[1][2]', - 'matrix[1][3]', - 'vector[1]', - 'matrix[2][1]', - 'matrix[2][2]', - 'matrix[2][3]', - 'vector[2]', - 'matrix[3][1]', - 'matrix[3][2]', - 'matrix[3][3]', - 'vector[3]' + "matrix[1][1]", + "matrix[1][2]", + "matrix[1][3]", + "vector[1]", + "matrix[2][1]", + "matrix[2][2]", + "matrix[2][3]", + "vector[2]", + "matrix[3][1]", + "matrix[3][2]", + "matrix[3][3]", + "vector[3]", ] - columns = [ - category[name].as_array().astype(float) for - name in matrix_columns - ] + columns = [category[name].as_array().astype(float) for name in matrix_columns] n = 4 if scale else 3 matrices = np.empty((len(columns[0]), n, 4), float) @@ -365,7 +360,7 @@ def _extract_matrices(category, scale=True): for column, coli, rowi in zip(columns, col_mask, row_mask): matrices[:, rowi, coli] = column - return dict(zip(category['id'].as_array(str), matrices)) + return dict(zip(category["id"].as_array(str), matrices)) def _chain_transformations(rotations, translations): @@ -401,10 +396,7 @@ def _get_transformations(struct_oper): for index, id in enumerate(struct_oper["id"].as_array()): rotation_matrix = np.array( [ - [ - float(struct_oper[f"matrix[{i}][{j}]"][index]) - for j in (1, 2, 3) - ] + [float(struct_oper[f"matrix[{i}][{j}]"][index]) for j in (1, 2, 3)] for i in (1, 2, 3) ] ) @@ -437,17 +429,14 @@ def _parse_operation_expression(expression): if "-" in gexpr: first, last = gexpr.split("-") operations.append( - [str(id) - for id in range(int(first), int(last) + 1)] + [str(id) for id in range(int(first), int(last) + 1)] ) else: operations.append([gexpr]) else: # Range of operation IDs, they must be integers first, last = expr.split("-") - operations.append( - [str(id) for id in range(int(first), int(last) + 1)] - ) + operations.append([str(id) for id in range(int(first), int(last) + 1)]) elif "," in expr: # List of operation IDs operations.append(expr.split(",")) diff --git a/molecularnodes/io/retrieve.py b/molecularnodes/io/retrieve.py index 533b2926..761d8a47 100644 --- a/molecularnodes/io/retrieve.py +++ b/molecularnodes/io/retrieve.py @@ -11,12 +11,15 @@ class FileDownloadPDBError(Exception): message -- explanation of the error """ - def __init__(self, message="There was an error downloading the file from the Protein Data Bank. PDB or format for PDB code may not be available."): + def __init__( + self, + message="There was an error downloading the file from the Protein Data Bank. PDB or format for PDB code may not be available.", + ): self.message = message super().__init__(self.message) -def download(code, format="cif", cache=None, database='rcsb'): +def download(code, format="cif", cache=None, database="rcsb"): """ Downloads a structure from the specified protein data bank in the given format. @@ -25,7 +28,7 @@ def download(code, format="cif", cache=None, database='rcsb'): code : str The code of the file to fetch. format : str, optional - The format of the file. Defaults to "cif". Possible values are ['cif', 'pdb', + The format of the file. Defaults to "cif". Possible values are ['cif', 'pdb', 'mmcif', 'pdbx', 'bcif']. cache : str, optional The cache directory to store the fetched file. Defaults to None. @@ -42,12 +45,11 @@ def download(code, format="cif", cache=None, database='rcsb'): ValueError If the specified format is not supported. """ - supported_formats = ['cif', 'pdb', 'bcif'] + supported_formats = ["cif", "pdb", "bcif"] if format not in supported_formats: - raise ValueError( - f"File format '{format}' not in: {supported_formats=}") + raise ValueError(f"File format '{format}' not in: {supported_formats=}") - _is_binary = (format in ['bcif']) + _is_binary = format in ["bcif"] filename = f"{code}.{format}" # create the cache location if cache: @@ -93,7 +95,7 @@ def _url(code, format, database="rcsb"): return f"https://files.rcsb.org/download/{code}.{format}" # if database == "pdbe": # return f"https://www.ebi.ac.uk/pdbe/entry-files/download/{filename}" - elif database == 'alphafold': + elif database == "alphafold": return get_alphafold_url(code, format) # if database == "pdbe": # return f"https://www.ebi.ac.uk/pdbe/entry-files/download/{filename}" @@ -102,18 +104,16 @@ def _url(code, format, database="rcsb"): def get_alphafold_url(code, format): - if format not in ['pdb', 'cif', 'bcif']: - ValueError( - f'Format {format} not currently supported from AlphaFold databse.' - ) + if format not in ["pdb", "cif", "bcif"]: + ValueError(f"Format {format} not currently supported from AlphaFold databse.") # we have to first query the database, then they'll return some JSON with a list # of metadata, some items of which will be the URLs for the computed models # in different formats such as pdbUrl, cifUrl, bcifUrl url = f"https://alphafold.ebi.ac.uk/api/prediction/{code}" - print(f'{url=}') + print(f"{url=}") response = requests.get(url) print(f"{response=}") data = response.json()[0] # return data[f'{format}Url'] - return data[f'{format}Url'] + return data[f"{format}Url"] diff --git a/molecularnodes/io/star.py b/molecularnodes/io/star.py index 5b6743ca..e11793ac 100644 --- a/molecularnodes/io/star.py +++ b/molecularnodes/io/star.py @@ -2,29 +2,22 @@ from . import parse bpy.types.Scene.MN_import_star_file_path = bpy.props.StringProperty( - name='File', - description='File path for the `.star` file to import.', - subtype='FILE_PATH', - maxlen=0 + name="File", + description="File path for the `.star` file to import.", + subtype="FILE_PATH", + maxlen=0, ) bpy.types.Scene.MN_import_star_file_name = bpy.props.StringProperty( - name='Name', - description='Name of the created object.', - default='NewStarInstances', - maxlen=0 + name="Name", + description="Name of the created object.", + default="NewStarInstances", + maxlen=0, ) -def load( - file_path, - name='NewStarInstances', - node_setup=True, - world_scale=0.01 -): - +def load(file_path, name="NewStarInstances", node_setup=True, world_scale=0.01): ensemble = parse.StarFile.from_starfile(file_path) - ensemble.create_model(name=name, node_setup=node_setup, - world_scale=world_scale) + ensemble.create_model(name=name, node_setup=node_setup, world_scale=world_scale) return ensemble @@ -32,7 +25,9 @@ def load( class MN_OT_Import_Star_File(bpy.types.Operator): bl_idname = "mn.import_star_file" bl_label = "Load" - bl_description = "Will import the given file, setting up the points to instance an object." + bl_description = ( + "Will import the given file, setting up the points to instance an object." + ) bl_options = {"REGISTER"} @classmethod @@ -44,15 +39,15 @@ def execute(self, context): load( file_path=scene.MN_import_star_file_path, name=scene.MN_import_star_file_name, - node_setup=True + node_setup=True, ) return {"FINISHED"} def panel(layout, scene): - layout.label(text="Load Star File", icon='FILE_TICK') + layout.label(text="Load Star File", icon="FILE_TICK") layout.separator() row_import = layout.row() - row_import.prop(scene, 'MN_import_star_file_name') - layout.prop(scene, 'MN_import_star_file_path') - row_import.operator('mn.import_star_file') + row_import.prop(scene, "MN_import_star_file_name") + layout.prop(scene, "MN_import_star_file_path") + row_import.operator("mn.import_star_file") diff --git a/molecularnodes/io/wwpdb.py b/molecularnodes/io/wwpdb.py index e56eb5e5..1f2c3afb 100644 --- a/molecularnodes/io/wwpdb.py +++ b/molecularnodes/io/wwpdb.py @@ -1,30 +1,26 @@ -import bpy from pathlib import Path + +import bpy + from . import parse -from .retrieve import download, FileDownloadPDBError -from requests import HTTPError +from .retrieve import FileDownloadPDBError, download def fetch( pdb_code, - style='spheres', - centre='', + style="spheres", + centre="", del_solvent=True, cache_dir=None, build_assembly=False, - format="bcif" + format="bcif", ): - if build_assembly: - centre = '' + centre = "" file_path = download(code=pdb_code, format=format, cache=cache_dir) - parsers = { - 'pdb': parse.PDB, - 'cif': parse.CIF, - 'bcif': parse.BCIF - } + parsers = {"pdb": parse.PDB, "cif": parse.CIF, "bcif": parse.BCIF} molecule = parsers[format](file_path=file_path) model = molecule.create_model( @@ -32,48 +28,50 @@ def fetch( centre=centre, style=style, del_solvent=del_solvent, - build_assembly=build_assembly + build_assembly=build_assembly, ) - model.mn['pdb_code'] = pdb_code - model.mn['molecule_type'] = format + model.mn["pdb_code"] = pdb_code + model.mn["molecule_type"] = format return molecule + # Properties that can be set in the scene, to be passed to the operator bpy.types.Scene.MN_pdb_code = bpy.props.StringProperty( - name='PDB', - description='The 4-character PDB code to download', - options={'TEXTEDIT_UPDATE'}, - maxlen=4 + name="PDB", + description="The 4-character PDB code to download", + options={"TEXTEDIT_UPDATE"}, + maxlen=4, ) bpy.types.Scene.MN_cache_dir = bpy.props.StringProperty( - name='', - description='Directory to save the downloaded files', - options={'TEXTEDIT_UPDATE'}, - default=str(Path('~', '.MolecularNodes').expanduser()), - subtype='DIR_PATH' + name="", + description="Directory to save the downloaded files", + options={"TEXTEDIT_UPDATE"}, + default=str(Path("~", ".MolecularNodes").expanduser()), + subtype="DIR_PATH", ) bpy.types.Scene.MN_cache = bpy.props.BoolProperty( name="Cache Downloads", description="Save the downloaded file in the given directory", - default=True + default=True, ) bpy.types.Scene.MN_import_format_download = bpy.props.EnumProperty( name="Format", description="Format to download as from the PDB", items=( ("bcif", ".bcif", "Binary compressed .cif file, fastest for downloading"), - ("cif", ".cif", 'The new standard of .cif / .mmcif'), - ("pdb", ".pdb", "The classic (and depcrecated) PDB format") - ) + ("cif", ".cif", "The new standard of .cif / .mmcif"), + ("pdb", ".pdb", "The classic (and depcrecated) PDB format"), + ), ) # operator that is called by the 'button' press which calls the fetch function + class MN_OT_Import_wwPDB(bpy.types.Operator): bl_idname = "mn.import_wwpdb" bl_label = "Fetch" @@ -93,7 +91,7 @@ def execute(self, context): if scene.MN_import_node_setup: style = scene.MN_import_style - centre = '' + centre = "" if scene.MN_import_centre: centre = scene.MN_centre_type @@ -105,40 +103,41 @@ def execute(self, context): style=style, cache_dir=cache_dir, build_assembly=scene.MN_import_build_assembly, - format=file_format + format=file_format, ) except FileDownloadPDBError as e: - self.report({'ERROR'}, str(e)) - if file_format == 'pdb': + self.report({"ERROR"}, str(e)) + if file_format == "pdb": self.report( - {'ERROR'}, 'There may not be a `.pdb` formatted file available - try a different download format.') + {"ERROR"}, + "There may not be a `.pdb` formatted file available - try a different download format.", + ) return {"CANCELLED"} bpy.context.view_layer.objects.active = mol.object - self.report( - {'INFO'}, message=f"Imported '{pdb_code}' as {mol.object.name}") + self.report({"INFO"}, message=f"Imported '{pdb_code}' as {mol.object.name}") return {"FINISHED"} + # the UI for the panel, which will display the operator and the properties def panel(layout, scene): - layout.label(text="Download from PDB", icon="IMPORT") layout.separator() row_import = layout.row().split(factor=0.5) - row_import.prop(scene, 'MN_pdb_code') + row_import.prop(scene, "MN_pdb_code") download = row_import.split(factor=0.3) - download.prop(scene, 'MN_import_format_download', text="") - download.operator('mn.import_wwpdb') + download.prop(scene, "MN_import_format_download", text="") + download.operator("mn.import_wwpdb") layout.separator(factor=0.4) row = layout.row().split(factor=0.3) - row.prop(scene, 'MN_cache') + row.prop(scene, "MN_cache") row_cache = row.row() - row_cache.prop(scene, 'MN_cache_dir') + row_cache.prop(scene, "MN_cache_dir") row_cache.enabled = scene.MN_cache layout.separator() @@ -146,18 +145,18 @@ def panel(layout, scene): options = layout.column(align=True) row = options.row() - row.prop(scene, 'MN_import_node_setup', text='') + row.prop(scene, "MN_import_node_setup", text="") col = row.column() - col.prop(scene, 'MN_import_style') + col.prop(scene, "MN_import_style") col.enabled = scene.MN_import_node_setup row_centre = options.row() - row_centre.prop(scene, 'MN_import_centre', icon_value=0) + row_centre.prop(scene, "MN_import_centre", icon_value=0) col_centre = row_centre.column() - col_centre.prop(scene, 'MN_centre_type', text='') + col_centre.prop(scene, "MN_centre_type", text="") col_centre.enabled = scene.MN_import_centre options.separator() grid = options.grid_flow() - grid.prop(scene, 'MN_import_build_assembly') - grid.prop(scene, 'MN_import_del_solvent') + grid.prop(scene, "MN_import_build_assembly") + grid.prop(scene, "MN_import_del_solvent") diff --git a/molecularnodes/pkg.py b/molecularnodes/pkg.py index 8c37d9af..1b61cad4 100644 --- a/molecularnodes/pkg.py +++ b/molecularnodes/pkg.py @@ -15,10 +15,10 @@ PYPI_MIRROR = { # the original. - 'Default': '', + "Default": "", # two mirrors in China Mainland to help those poor victims under GFW. - 'BFSU (Beijing)': 'https://mirrors.bfsu.edu.cn/pypi/web/simple', - 'TUNA (Beijing)': 'https://pypi.tuna.tsinghua.edu.cn/simple', + "BFSU (Beijing)": "https://mirrors.bfsu.edu.cn/pypi/web/simple", + "TUNA (Beijing)": "https://pypi.tuna.tsinghua.edu.cn/simple", # append more if necessary. } """ @@ -26,7 +26,7 @@ """ -def start_logging(logfile_name: str = 'side-packages-install') -> logging.Logger: +def start_logging(logfile_name: str = "side-packages-install") -> logging.Logger: """ Configure and start logging to a file. @@ -47,7 +47,7 @@ def start_logging(logfile_name: str = 'side-packages-install') -> logging.Logger """ # Create the logs directory if it doesn't exist - logs_dir = os.path.join(os.path.abspath(ADDON_DIR), 'logs') + logs_dir = os.path.join(os.path.abspath(ADDON_DIR), "logs") os.makedirs(logs_dir, exist_ok=True) # Set up logging configuration @@ -100,13 +100,12 @@ def process_pypi_mirror_to_url(pypi_mirror_provider: str) -> str: If the provided PyPI mirror provider is invalid. """ - if pypi_mirror_provider.startswith('https:'): + if pypi_mirror_provider.startswith("https:"): return pypi_mirror_provider elif pypi_mirror_provider in PYPI_MIRROR.keys(): return PYPI_MIRROR[pypi_mirror_provider] else: - raise ValueError( - f"Invalid PyPI mirror provider: {pypi_mirror_provider}") + raise ValueError(f"Invalid PyPI mirror provider: {pypi_mirror_provider}") def get_pkgs(requirements: str = None) -> dict: @@ -164,13 +163,13 @@ def get_pkgs(requirements: str = None) -> dict: pkgs = {} for line in lines: try: - pkg, desc = line.split('#') - pkg_meta = pkg.split('==') + pkg, desc = line.split("#") + pkg_meta = pkg.split("==") name = pkg_meta[0].strip() pkgs[name] = { "name": name, "version": pkg_meta[1].strip(), - "desc": desc.strip() + "desc": desc.strip(), } except ValueError: # Skip line if it doesn't have the expected format @@ -197,12 +196,12 @@ def is_current(package: str) -> bool: pkg = get_pkgs().get(package) try: available_version = get_version(package) - return available_version == pkg['version'] + return available_version == pkg["version"] except PackageNotFoundError: return False -def run_python(cmd_list: list = None, mirror_url: str = '', timeout: int = 600): +def run_python(cmd_list: list = None, mirror_url: str = "", timeout: int = 600): """ Runs pip command using the specified command list and returns the command output. @@ -240,28 +239,29 @@ def run_python(cmd_list: list = None, mirror_url: str = '', timeout: int = 600): cmd_list = [python_exe] + cmd_list # add mirror to the command list if it's valid - if mirror_url and mirror_url.startswith('https'): - cmd_list += ['-i', mirror_url] + if mirror_url and mirror_url.startswith("https"): + cmd_list += ["-i", mirror_url] log = start_logging() log.info(f"Running Pip: '{cmd_list}'") # run the command and capture the output - result = subprocess.run(cmd_list, timeout=timeout, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = subprocess.run( + cmd_list, timeout=timeout, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) if result.returncode != 0: - log.error('Command failed: %s', cmd_list) - log.error('stdout: %s', result.stdout.decode()) - log.error('stderr: %s', result.stderr.decode()) + log.error("Command failed: %s", cmd_list) + log.error("stdout: %s", result.stdout.decode()) + log.error("stderr: %s", result.stderr.decode()) else: - log.info('Command succeeded: %s', cmd_list) - log.info('stdout: %s', result.stdout.decode()) + log.info("Command succeeded: %s", cmd_list) + log.info("stdout: %s", result.stdout.decode()) # return the command list, return code, stdout, and stderr as a tuple return result -def install_package(package: str, pypi_mirror_provider: str = 'Default') -> list: +def install_package(package: str, pypi_mirror_provider: str = "Default") -> list: """ Install a Python package and its dependencies using pip. @@ -296,15 +296,12 @@ def install_package(package: str, pypi_mirror_provider: str = 'Default') -> list print(f"Installing {package}...") - mirror_url = process_pypi_mirror_to_url( - pypi_mirror_provider=pypi_mirror_provider) + mirror_url = process_pypi_mirror_to_url(pypi_mirror_provider=pypi_mirror_provider) print(f"Using PyPI mirror: {pypi_mirror_provider} {mirror_url}") - run_python(["-m", "ensurepip"]), - run_python(["-m", "pip", "install", "--upgrade", "pip"], - mirror_url=mirror_url) - result = run_python(["-m", "pip", "install", package], - mirror_url=mirror_url) + (run_python(["-m", "ensurepip"]),) + run_python(["-m", "pip", "install", "--upgrade", "pip"], mirror_url=mirror_url) + result = run_python(["-m", "pip", "install", package], mirror_url=mirror_url) return result @@ -328,7 +325,7 @@ def __init__(self, package_name, error_message): super().__init__(f"Failed to install {package_name}: {error_message}") -def install_all_packages(pypi_mirror_provider: str = 'Default') -> list: +def install_all_packages(pypi_mirror_provider: str = "Default") -> list: """ Install all packages listed in the 'requirements.txt' file. @@ -356,42 +353,40 @@ def install_all_packages(pypi_mirror_provider: str = 'Default') -> list: ``` """ - mirror_url = process_pypi_mirror_to_url( - pypi_mirror_provider=pypi_mirror_provider) + mirror_url = process_pypi_mirror_to_url(pypi_mirror_provider=pypi_mirror_provider) pkgs = get_pkgs() results = [] for pkg in pkgs.items(): - try: - result = install_package(package=f"{pkg.get('name')}=={pkg.get('version')}", - pypi_mirror_provider=mirror_url) + result = install_package( + package=f"{pkg.get('name')}=={pkg.get('version')}", + pypi_mirror_provider=mirror_url, + ) results.append(result) except InstallationError as e: raise InstallationError( - f"Error installing package {pkg.get('name')}: {str(e)}") + f"Error installing package {pkg.get('name')}: {str(e)}" + ) return results class MN_OT_Install_Package(bpy.types.Operator): - bl_idname = 'mn.install_package' - bl_label = 'Install Given Python Package' - bl_options = {'REGISTER', 'INTERNAL'} + bl_idname = "mn.install_package" + bl_label = "Install Given Python Package" + bl_options = {"REGISTER", "INTERNAL"} package: bpy.props.StringProperty( - name='Python Package', - description='Python Package to Install', - default='biotite' - ) + name="Python Package", + description="Python Package to Install", + default="biotite", + ) # type: ignore version: bpy.props.StringProperty( - name='Python Package', - description='Python Package to Install', - default='0.36.1' - ) + name="Python Package", description="Python Package to Install", default="0.36.1" + ) # type: ignore description: bpy.props.StringProperty( - name='Operator description', - default='Install specified python package.' - ) + name="Operator description", default="Install specified python package." + ) # type: ignore @classmethod def description(cls, context, properties): @@ -399,37 +394,38 @@ def description(cls, context, properties): def execute(self, context): installable = f"{self.package}=={self.version}" - result = install_package(package=installable, - pypi_mirror_provider=bpy.context.scene.pypi_mirror_provider) + result = install_package( + package=installable, + pypi_mirror_provider=bpy.context.scene.pypi_mirror_provider, + ) if result.returncode == 0 and is_current(self.package): self.report( - {'INFO'}, - f"Successfully installed {self.package} v{self.version}" + {"INFO"}, f"Successfully installed {self.package} v{self.version}" ) else: - log_dir = os.path.join(os.path.abspath(ADDON_DIR), 'logs') + log_dir = os.path.join(os.path.abspath(ADDON_DIR), "logs") self.report( - {'ERROR'}, - f"Error installing package. Please check the log files in '{log_dir}'." + {"ERROR"}, + f"Error installing package. Please check the log files in '{log_dir}'.", ) - return {'FINISHED'} + return {"FINISHED"} -def button_install_pkg(layout, name, version, desc=''): +def button_install_pkg(layout, name, version, desc=""): layout = layout.row() if is_current(name): row = layout.row() row.label(text=f"{name} version {version} is installed.") - op = row.operator('mn.install_package', text=f'Reinstall {name}') + op = row.operator("mn.install_package", text=f"Reinstall {name}") op.package = name op.version = version - op.description = f'Reinstall {name}' + op.description = f"Reinstall {name}" else: row = layout.row(heading=f"Package: {name}") col = row.column() col.label(text=str(desc)) col = row.column() - op = col.operator('mn.install_package', text=f'Install {name}') + op = col.operator("mn.install_package", text=f"Install {name}") op.package = name op.version = version - op.description = f'Install required python package: {name}' + op.description = f"Install required python package: {name}" diff --git a/molecularnodes/props.py b/molecularnodes/props.py index c6234393..316c6fa6 100644 --- a/molecularnodes/props.py +++ b/molecularnodes/props.py @@ -4,38 +4,46 @@ bpy.types.Scene.MN_import_centre = bpy.props.BoolProperty( name="Centre Structure", description="Move the imported Molecule on the World Origin", - default=False + default=False, ) bpy.types.Scene.MN_centre_type = bpy.props.EnumProperty( name="Method", - default='mass', + default="mass", items=( - ('mass', "Mass", "Adjust the structure's centre of mass to be at the world origin", 1), - ('centroid', "Centroid", - "Adjust the structure's centroid (centre of geometry) to be at the world origin", 2) - ) + ( + "mass", + "Mass", + "Adjust the structure's centre of mass to be at the world origin", + 1, + ), + ( + "centroid", + "Centroid", + "Adjust the structure's centroid (centre of geometry) to be at the world origin", + 2, + ), + ), ) bpy.types.Scene.MN_import_del_solvent = bpy.props.BoolProperty( name="Remove Solvent", description="Delete the solvent from the structure on import", - default=True + default=True, ) bpy.types.Scene.MN_import_panel_selection = bpy.props.IntProperty( name="MN_import_panel_selection", description="Import Panel Selection", - subtype='NONE', - default=0 + subtype="NONE", + default=0, ) bpy.types.Scene.MN_import_build_assembly = bpy.props.BoolProperty( - name='Build Assembly', - default=False + name="Build Assembly", default=False ) bpy.types.Scene.MN_import_node_setup = bpy.props.BoolProperty( name="Setup Nodes", default=True, - description='Create and set up a Geometry Nodes tree on import' + description="Create and set up a Geometry Nodes tree on import", ) @@ -43,16 +51,16 @@ class MolecularNodesObjectProperties(bpy.types.PropertyGroup): subframes: bpy.props.IntProperty( name="Subframes", description="Number of subframes to interpolate for MD trajectories", - default=0 - ) + default=0, + ) # type: ignore molecule_type: bpy.props.StringProperty( name="Molecular Type", description="How the file was imported, dictating how MN interacts with it", - default="" - ) + default="", + ) # type: ignore pdb_code: bpy.props.StringProperty( name="PDB", description="PDB code used to download this structure", maxlen=4, - options={'HIDDEN'} - ) + options={"HIDDEN"}, + ) # type: ignore diff --git a/molecularnodes/ui/func.py b/molecularnodes/ui/func.py index d1d20b89..090fa5db 100644 --- a/molecularnodes/ui/func.py +++ b/molecularnodes/ui/func.py @@ -7,31 +7,32 @@ def build_menu(layout, items): # print(item) if item == "break": layout.separator() - elif item['label'] == "custom": - for button in item['values']: - item['function'](layout, - label=button['label'], - field=button['field'], - prefix=button['prefix'], - property_id=button['property_id'] - ) - elif item['name'].startswith("mn."): - layout.operator(item['name']) + elif item["label"] == "custom": + for button in item["values"]: + item["function"]( + layout, + label=button["label"], + field=button["field"], + prefix=button["prefix"], + property_id=button["property_id"], + ) + elif item["name"].startswith("mn."): + layout.operator(item["name"]) else: - label = item['label'] - name = item['name'] - description = item['description'].split('\n')[0].removesuffix('.') - menu_item_interface(layout, label=label, - name=name, description=description) - - -def menu_item_interface(layout_function, - label, - name, - description='Add custom MolecularNodes node group.', - node_link=False - ): - op = layout_function.operator('mn.add_custom_node_group', text=label) + label = item["label"] + name = item["name"] + description = item["description"].split("\n")[0].removesuffix(".") + menu_item_interface(layout, label=label, name=name, description=description) + + +def menu_item_interface( + layout_function, + label, + name, + description="Add custom MolecularNodes node group.", + node_link=False, +): + op = layout_function.operator("mn.add_custom_node_group", text=label) op.node_label = label op.node_name = name op.node_description = description @@ -39,7 +40,7 @@ def menu_item_interface(layout_function, def button_custom_color(layout, label, field, prefix, property_id, starting_value=0): - op = layout.operator('mn.color_custom', text=label) + op = layout.operator("mn.color_custom", text=label) op.field = field op.prefix = prefix op.node_property = property_id @@ -48,8 +49,10 @@ def button_custom_color(layout, label, field, prefix, property_id, starting_valu op.description = f"Choose individual colors for each {label}" -def button_custom_selection(layout, label, field, prefix, property_id, starting_value=0): - op = layout.operator('mn.selection_custom', text=label) +def button_custom_selection( + layout, label, field, prefix, property_id, starting_value=0 +): + op = layout.operator("mn.selection_custom", text=label) op.field = field op.prefix = prefix op.node_property = property_id diff --git a/molecularnodes/ui/node_info.py b/molecularnodes/ui/node_info.py index 2d78eaa4..8f0489dc 100644 --- a/molecularnodes/ui/node_info.py +++ b/molecularnodes/ui/node_info.py @@ -1,492 +1,478 @@ -from .func import ( - button_custom_color, - button_custom_selection -) +from .func import button_custom_color, button_custom_selection menu_items = { - 'style': [ + "style": [ { - 'label': 'Presets', - 'name': 'MN_style_presets', + "label": "Presets", + "name": "MN_style_presets", "description": "Quickly switch between several different pre-made preset styles. Best used when using MolecularNodes via scripts, ensuring all atoms are displayed using a combination of cartoons and atoms.", - "video_url": "https://imgur.com/gCQRWBk.mp4" + "video_url": "https://imgur.com/gCQRWBk.mp4", }, { - 'label': 'Spheres', - 'name': 'MN_style_spheres', + "label": "Spheres", + "name": "MN_style_spheres", "description": "Style to apply the traditional space-filling atomic representation of atoms. Spheres are scaled based on the `vdw_radii` attribute. By default the _Point Cloud_ rendering system is used, which is only visible inside of Cycles. By enabling 'EEVEE' it creates a sphere mesh object per atom. This makes it visible inside of EEVEE, but has poor performance at high atom counts.", - "video_url": "https://imgur.com/3anAJqz" + "video_url": "https://imgur.com/3anAJqz", }, { - 'label': 'Cartoon', - 'name': 'MN_style_cartoon', + "label": "Cartoon", + "name": "MN_style_cartoon", "description": "Style to apply the traditional cartoon representation of protein structures. This style highlights alpha-helices and beta-sheets with arrows and cylinders.", - "video_url": "https://imgur.com/1xmdfxZ" + "video_url": "https://imgur.com/1xmdfxZ", }, { - 'label': 'Ribbon', - 'name': 'MN_style_ribbon', + "label": "Ribbon", + "name": "MN_style_ribbon", "description": "Style that creates a continuous solid ribbon or licorice tube through the backbones of peptides and nucleic acids.", - "video_url": "https://imgur.com/iMxEJaH" + "video_url": "https://imgur.com/iMxEJaH", }, { - 'label': 'Surface', - 'name': 'MN_style_surface', + "label": "Surface", + "name": "MN_style_surface", "description": "Style that creates a surface representation based on the proximity of atoms to a probe that is moved through the entire structure.", - "video_url": "https://imgur.com/ER8pcYf" + "video_url": "https://imgur.com/ER8pcYf", }, { - 'label': 'Ball and Stick', - 'name': 'MN_style_ball_and_stick', + "label": "Ball and Stick", + "name": "MN_style_ball_and_stick", "description": "Style that creates cylinders for bonds and spheres for atoms. The atoms can be either Eevee or Cycles compatible, with customisation to resolution and radius possible.", - "video_url": "https://imgur.com/kuWuOsw" + "video_url": "https://imgur.com/kuWuOsw", }, { - 'label': 'Stick', - 'name': 'MN_style_stick', + "label": "Stick", + "name": "MN_style_stick", "description": "Style that creates a cylinder for each bond. Cylindrical caps to the cylinders are currently not supported. Best to use [`MN_style_ball_and_stick`](#style-ball-and-stick).", - "video_url": "https://imgur.com/tV4XalY" - } + "video_url": "https://imgur.com/tV4XalY", + }, ], - 'select': [ + "select": [ { - 'label': 'Separate Atoms', - 'name': 'MN_select_separate_atoms', - 'description': "Select only the desired input atoms. The output is bits of geometry, which include the selection and include the inverse of the selected atoms. You can expand the selection to include an entire residue if a single atom in that residue is selected, by setting `Whole Residue` to `True`.", - 'video_url': "https://imgur.com/VsCW0HY" + "label": "Separate Atoms", + "name": "MN_select_separate_atoms", + "description": "Select only the desired input atoms. The output is bits of geometry, which include the selection and include the inverse of the selected atoms. You can expand the selection to include an entire residue if a single atom in that residue is selected, by setting `Whole Residue` to `True`.", + "video_url": "https://imgur.com/VsCW0HY", }, { - 'label': 'Separate Polymers', - 'name': 'MN_select_separate_polymers', - 'description': "Separate the input atomic geometry into it's different polymers or `Protein`, `Nucleic Acid` and `other`.", - 'video_url': 'https://imgur.com/ICQZxxz' + "label": "Separate Polymers", + "name": "MN_select_separate_polymers", + "description": "Separate the input atomic geometry into it's different polymers or `Protein`, `Nucleic Acid` and `other`.", + "video_url": "https://imgur.com/ICQZxxz", }, "break", { - 'label': 'custom', - 'function': button_custom_selection, - 'values': [ + "label": "custom", + "function": button_custom_selection, + "values": [ { - 'label': 'Chain', - 'field': 'chain_id', - 'name': 'MN_select_chain_', - 'prefix': 'Chain ', - 'property_id': 'chain_ids', + "label": "Chain", + "field": "chain_id", + "name": "MN_select_chain_", + "prefix": "Chain ", + "property_id": "chain_ids", "description": "Select single or multiple of the different chains. Creates a selection based on the `chain_id` attribute.", - "video_url": "https://imgur.com/P9ZVT2Z" + "video_url": "https://imgur.com/P9ZVT2Z", }, { - 'label': 'Entity', - 'field': 'entity_id', - 'name': 'MN_select_entity_', - 'prefix': '', - 'property_id': 'entity_ids', + "label": "Entity", + "field": "entity_id", + "name": "MN_select_entity_", + "prefix": "", + "property_id": "entity_ids", "description": "Select single or multiple of the different entities. Creates a selection based on the `entity_id` attribute.", - "video_url": "https://imgur.com/fKQIfGZ" + "video_url": "https://imgur.com/fKQIfGZ", }, { - 'label': 'Ligand', - 'field': 'res_name', - 'name': 'MN_select_ligand_', - 'prefix': '', - 'property_id': 'ligands', + "label": "Ligand", + "field": "res_name", + "name": "MN_select_ligand_", + "prefix": "", + "property_id": "ligands", "description": "Select single or multiple of the different ligands.", - "video_url": "https://imgur.com/s2seWIw" - } - ] + "video_url": "https://imgur.com/s2seWIw", + }, + ], }, "break", { - 'label': 'Cube', - 'name': 'MN_select_cube', + "label": "Cube", + "name": "MN_select_cube", "description": "Create a selection that is inside the `Empty_Cube` object. When this node is first created, an _empty_ object called `Empty_Cube` should be created. You can always create additional empty objects through the add menu, to use a different object. The rotation and scale of the object will be taken into account for the selection.", - "video_url": "https://imgur.com/P4GZ7vq" + "video_url": "https://imgur.com/P4GZ7vq", }, { - 'label': 'Sphere', - 'name': 'MN_select_sphere', + "label": "Sphere", + "name": "MN_select_sphere", "description": "Create a selection that is within a spherical radius of an object, based on that object's scale. By default an _empty_ object called `Empty_Sphere` is created. You can use other objects or create a new empty to use. The origin point for the object will be used, which should be taken in to account when using molecules. Use [`MN_select_proximity`](#select-proximity) for selections which are within a certain distance of a selection of atoms instead of a single origin point.", - "video_url": "https://imgur.com/xdeTZR7" + "video_url": "https://imgur.com/xdeTZR7", }, "break", { - 'label': 'Secondary Structure', - 'name': 'MN_select_sec_struct', + "label": "Secondary Structure", + "name": "MN_select_sec_struct", # or can be calculated using the [`MN_utils_dssp'](#utils-dssp) node.", "description": "Select based on the assigned secondary structure information. Only returns a selection if the `sec_struct` attribute exists on the atoms. Will be imported from files where it is present", - "video_url": "https://imgur.com/IindS3D" + "video_url": "https://imgur.com/IindS3D", }, { - 'label': 'Backbone', - 'name': 'MN_select_backbone', + "label": "Backbone", + "name": "MN_select_backbone", "description": "Selection fields for the backbone and side chains of the protein and nucleic acids.", - "video_url": "https://imgur.com/Sbl6ns5" + "video_url": "https://imgur.com/Sbl6ns5", }, { - 'label': 'Atomic Number', - 'name': 'MN_select_atomic_number', - 'description': "Select single elements, by matching to the `atomic_number` field. Useful for selecting single elements, or combining to select elements higher than 20 on the periodic table.", - 'video_url': "https://imgur.com/Bxn33YK" + "label": "Atomic Number", + "name": "MN_select_atomic_number", + "description": "Select single elements, by matching to the `atomic_number` field. Useful for selecting single elements, or combining to select elements higher than 20 on the periodic table.", + "video_url": "https://imgur.com/Bxn33YK", }, { - 'label': 'Element', - 'name': 'MN_select_element', + "label": "Element", + "name": "MN_select_element", "description": "Select individual elements, for the first 20 elements on the periodic table. For selections of higher elements, use [`MN_select_atomic_number`](#select-atomic-number). Creating a node which includes more elements becomes too large to be practical.", - "video_url": "https://imgur.com/nRQwamG" + "video_url": "https://imgur.com/nRQwamG", }, { - 'label': 'Attribute', - 'name': 'MN_select_attribute', + "label": "Attribute", + "name": "MN_select_attribute", "description": "Selections based on the different attributes that are available on the atomic geometry.", - "video_url": "https://imgur.com/HakZ4sx" + "video_url": "https://imgur.com/HakZ4sx", }, { - 'label': 'Bonded Atoms', - 'name': 'MN_select_bonded', + "label": "Bonded Atoms", + "name": "MN_select_bonded", "description": "Based on an initial selection, finds atoms which are within a certain number of bonds of this selection. Output can include or excluded the original selection.", - "video_url": "https://imgur.com/g8hgXup" + "video_url": "https://imgur.com/g8hgXup", }, "break", { - 'label': 'Res ID', - 'name': 'mn.residues_selection_custom', - 'backup': 'MN_select_res_id_', + "label": "Res ID", + "name": "mn.residues_selection_custom", + "backup": "MN_select_res_id_", "description": "Create a more complex selection for the `res_id` field, by specifying multiple ranges and potential single `res_id` numbers. This node is built uniquely each time, to the inputs will look different for each user.\nIn the example below, residues 10 & 15 are selected, as well as residues between and including 20-100.\nThe node was created by inputting `10, 15, 20-100` into the node creation field.", - "video_url": "https://imgur.com/OwAXsbG" + "video_url": "https://imgur.com/OwAXsbG", }, { - 'label': 'Proximity', - 'name': 'MN_select_proximity', + "label": "Proximity", + "name": "MN_select_proximity", "description": "Create a selection based on the proximity to the Target Atoms of the input. A sub-selection of the Target atoms can be used if the `Selection` input is used. You can expand the selection to include an entire residue if a single atom in that residue is selected, by setting `Whole Residue` to `True`.\nIn the example below, the `MN_style_atoms` is being applied to a selection, which is being calculated from the proximity of atoms to specific chains. As the cutoff for the selection is changed, it includes or excludes more atoms. The `Whole Residue` option also ensures that entire residues are shown.", - "video_url": "https://imgur.com/RI80CRY" + "video_url": "https://imgur.com/RI80CRY", }, { - 'label': 'Res ID Single', - 'name': 'MN_select_res_id_single', + "label": "Res ID Single", + "name": "MN_select_res_id_single", "description": "Select a single residue based on the `res_id` number.", - "video_url": "https://imgur.com/BL6AOP4" + "video_url": "https://imgur.com/BL6AOP4", }, { - 'label': 'Res ID Range', - 'name': 'MN_select_res_id_range', + "label": "Res ID Range", + "name": "MN_select_res_id_range", "description": "Select multiple residues by specifying a _minimum_ and a _maximum_ which will create the selection based on the `res_id` number.", - "video_url": "https://imgur.com/NdoQcdE" + "video_url": "https://imgur.com/NdoQcdE", }, { - 'label': 'Res Name Peptide', - 'name': 'MN_select_res_name_peptide', + "label": "Res Name Peptide", + "name": "MN_select_res_name_peptide", "description": "Select single or multiple protein residues by name. Includes the 20 naturally occurring amino acids.", - "video_url": "https://imgur.com/kjzH9Rs" + "video_url": "https://imgur.com/kjzH9Rs", }, { - 'label': 'Res Name Nucleic', - 'name': 'MN_select_res_name_nucleic', + "label": "Res Name Nucleic", + "name": "MN_select_res_name_nucleic", "description": "Select single or multiple nucleic residues by name.", - "video_url": "https://imgur.com/qnUlHpG" + "video_url": "https://imgur.com/qnUlHpG", }, { - 'label': 'Res Whole', - 'name': 'MN_select_res_whole', + "label": "Res Whole", + "name": "MN_select_res_whole", "description": "Expand the given selection to include a whole residue, if a single atom in that residue is selected. Useful for when a distance or proximity selection includes some of the residue and you wish to include all of the residue.", - "video_url": "https://imgur.com/JFzwE0i" + "video_url": "https://imgur.com/JFzwE0i", }, ], - 'color': [ + "color": [ { - 'label': 'Set Color', - 'name': 'MN_color_set', + "label": "Set Color", + "name": "MN_color_set", "description": "The is the primary way to change the color of structures in Molecular Nodes. Colors for cartoon and ribbon are taken from the _alpha-carbons_ of the structures. Change the color of the input atoms, based on a selection and a color field. The color field can be as complex of a calculation as you wish. In the example below the color for the whole structure can be set, or the color can be based on a color for each chain, or the result of mapping a color to an attribute such as `b_factor`.", - "video_url": "https://imgur.com/667jf0O" + "video_url": "https://imgur.com/667jf0O", }, "break", { - 'label': 'custom', - 'function': button_custom_color, - 'values': [ + "label": "custom", + "function": button_custom_color, + "values": [ { - 'label': 'Chain', - 'field': 'chain_id', - 'name': 'MN_select_chain_', - 'prefix': 'Chain', - 'property_id': 'chain_ids', + "label": "Chain", + "field": "chain_id", + "name": "MN_select_chain_", + "prefix": "Chain", + "property_id": "chain_ids", "description": "Choose the colors for individual chains in the structure. This node is generated for each particular molecule, so the inputs will look different based on the imported structure. For larger structures with many chains this node may become too large to be practical, in which case you might better use [`MN_color_entity_id`](#color-entity-id).", - "video_url": "https://imgur.com/9oM24vB" + "video_url": "https://imgur.com/9oM24vB", }, { - 'label': 'Entity', - 'field': 'entity_id', - 'name': 'MN_color_entity_', - 'prefix': '', - 'property_id': 'entity_ids', + "label": "Entity", + "field": "entity_id", + "name": "MN_color_entity_", + "prefix": "", + "property_id": "entity_ids", "description": "Choose the colors for individual entities in the structure. Multiple chains may be classified as the same entity, if they are copies of the same chain but in different conformations or positions and rotations. The nodes is generated for each individual structure, if `entity_id` is available.", - "video_url": "https://imgur.com/kEvj5Jk" + "video_url": "https://imgur.com/kEvj5Jk", }, { - 'label': 'Ligand', - 'field': 'res_name', - 'name': 'MN_color_ligand_', - 'prefix': '', - 'property_id': 'ligands', + "label": "Ligand", + "field": "res_name", + "name": "MN_color_ligand_", + "prefix": "", + "property_id": "ligands", "description": "Choose the colors for individual ligands in the structure.", - "video_url": "https://imgur.com/bQh8Fd9" - } - ] + "video_url": "https://imgur.com/bQh8Fd9", + }, + ], }, "break", { - 'label': 'Goodsell Colors', - 'name': 'MN_color_goodsell', + "label": "Goodsell Colors", + "name": "MN_color_goodsell", "description": "Change the inputted color to be darker for non-carbon atoms. Creates a _Goodsell Style_ color scheme for individual chains.", - "video_url": "https://imgur.com/gPgMSRa" + "video_url": "https://imgur.com/gPgMSRa", }, { - 'label': 'Attribute Map', - 'name': 'MN_color_attribute_map', + "label": "Attribute Map", + "name": "MN_color_attribute_map", "description": "Interpolate between two or three colors, based on the value of an attribute field such as `b_factor`. Choosing the minimum and maximum values with the inputs.", - "video_url": "https://imgur.com/lc2o6e1" + "video_url": "https://imgur.com/lc2o6e1", }, { - 'label': 'Attribute Random', - 'name': 'MN_color_attribute_random', + "label": "Attribute Random", + "name": "MN_color_attribute_random", "description": "Generate a random color, based on the given attribute. Control the lightness and saturation of the color with the inputs.", - "video_url": "https://imgur.com/5sMcpAu" + "video_url": "https://imgur.com/5sMcpAu", }, + {"label": "Backbone", "name": "MN_color_backbone", "description": ""}, { - 'label': 'Backbone', - 'name': 'MN_color_backbone', - 'description': "" + "label": "pLDTT", + "name": "MN_color_pLDTT", + "description": "Assigns colors using the `b_factor` attribute, which contains the `pLDTT` attribute for models that come from AlphaFold.", }, { - 'label': 'pLDTT', - 'name': 'MN_color_pLDTT', - 'description': 'Assigns colors using the `b_factor` attribute, which contains the `pLDTT` attribute for models that come from AlphaFold.' - }, - { - 'label': 'Secondary Structure', - 'name': 'MN_color_sec_struct', + "label": "Secondary Structure", + "name": "MN_color_sec_struct", "description": "Choose a color for the different secondary structures, based on the `sec_struct` attribute.", - "video_url": "https://imgur.com/wcJAUp9" + "video_url": "https://imgur.com/wcJAUp9", }, "break", { - 'label': 'Element', - 'name': 'MN_color_element', + "label": "Element", + "name": "MN_color_element", "description": "Choose a color for each of the first 20 elements on the periodic table. For higher atomic number elements use [`MN_color_atomic_number`](#color-atomic-number).", - "video_url": "https://imgur.com/iMGZKCx" + "video_url": "https://imgur.com/iMGZKCx", }, { - 'label': 'Atomic Number', - 'name': 'MN_color_atomic_number', + "label": "Atomic Number", + "name": "MN_color_atomic_number", "description": "Choose a color for an individual element. Select the element based on `atomic_number`. Useful for higher atomic number elements which are less commonly found in structures.", - "video_url": "https://imgur.com/pAloaAF" + "video_url": "https://imgur.com/pAloaAF", }, { - 'label': 'Res Name Peptide', - 'name': 'MN_color_res_name_peptide', + "label": "Res Name Peptide", + "name": "MN_color_res_name_peptide", "description": "Choose a color for each of the 20 naturally occurring amino acids. Non AA atoms will retain their currently set color.", - "video_url": "https://imgur.com/1yhSVsW" + "video_url": "https://imgur.com/1yhSVsW", }, { - 'label': 'Res Name Nucleic', - 'name': 'MN_color_res_name_nucleic', + "label": "Res Name Nucleic", + "name": "MN_color_res_name_nucleic", "description": "Choose a color for each of the nucleic acids. Non nucleic acid atoms will retain their currently set color.", - "video_url": "https://imgur.com/LpLZT3F" + "video_url": "https://imgur.com/LpLZT3F", }, - { - 'label': 'Element Common', - 'name': 'MN_color_common', + "label": "Element Common", + "name": "MN_color_common", "description": "Choose a color for each of the common elements. This is a smaller convenience node for elements which commonly appear in macromolecular structures. Use [`MN_color_element`](#color-element) for the first 20 elements and [`MN_color_atomic_number`](#color-atomic-number) for individual elements with higher atomic numbers.", - "video_url": "https://imgur.com/GhLdNwy" + "video_url": "https://imgur.com/GhLdNwy", }, ], - - 'topology': [ + "topology": [ { - 'label': 'Find Bonds', - 'name': 'MN_topo_bonds_find', - 'description': "Finds bonds between atoms based on distance. Based on the vdw_radii for each point, finds other points within a certain radius to create a bond to. Does not preserve the index for the points, detect bond type, or transfer all attributes", - 'video_url': 'https://imgur.com/oUo5TsM' + "label": "Find Bonds", + "name": "MN_topo_bonds_find", + "description": "Finds bonds between atoms based on distance. Based on the vdw_radii for each point, finds other points within a certain radius to create a bond to. Does not preserve the index for the points, detect bond type, or transfer all attributes", + "video_url": "https://imgur.com/oUo5TsM", }, { - 'label': 'Break Bonds', - 'name': 'MN_topo_bonds_break', - 'description': "Will delete a bond between atoms that already exists based on a distance cutoff, or is selected in the `Selection` input. Leaves the atoms unaffected", - 'video_url': 'https://imgur.com/n8cTN0k' + "label": "Break Bonds", + "name": "MN_topo_bonds_break", + "description": "Will delete a bond between atoms that already exists based on a distance cutoff, or is selected in the `Selection` input. Leaves the atoms unaffected", + "video_url": "https://imgur.com/n8cTN0k", }, { - 'label': 'Edge Info', - 'name': 'MN_topo_edge_info', - 'description': 'Get information for the selected edge, evaluated on the point domain. The "Edge Index" selects the edge from all possible connected edges. Edges are unfortunately stored somewhat randomly. The resulting information is between the evaluating point and the point that the edge is between. Point Index returns -1 if not connected.\n\nIn the video example, cones are instanced on each point where the Edge Index returns a valid connection. The Edge Vector can be used to align the instanced cone along that edge. The length of the edge can be used to scale the cone to the other point. As the "Edge Index" is changed, the selected edge changes. When "Edge Index" == 3, only the atoms with 4 connections are selected, which in this model (1BNA) are just the phosphates.', - 'video_url': "https://imgur.com/Ykyis3e" + "label": "Edge Info", + "name": "MN_topo_edge_info", + "description": 'Get information for the selected edge, evaluated on the point domain. The "Edge Index" selects the edge from all possible connected edges. Edges are unfortunately stored somewhat randomly. The resulting information is between the evaluating point and the point that the edge is between. Point Index returns -1 if not connected.\n\nIn the video example, cones are instanced on each point where the Edge Index returns a valid connection. The Edge Vector can be used to align the instanced cone along that edge. The length of the edge can be used to scale the cone to the other point. As the "Edge Index" is changed, the selected edge changes. When "Edge Index" == 3, only the atoms with 4 connections are selected, which in this model (1BNA) are just the phosphates.', + "video_url": "https://imgur.com/Ykyis3e", }, { - 'label': 'Edge Angle', - 'name': 'MN_topo_edge_angle', - 'description': ' Calculate the angle between two edges, selected with the edge indices. For molecule bonds, combinations of [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] will select all possible bond angles.\n\nIn the video example, two edges are selected with their "Edge Index" values. Those atoms which aren\'t valid return false and do not get instanced. The two edge vectors are used to calculate the perpendicular vector through cross product, around which the rotation for the cone is rotated. This demonstrates the ability to calculate the edge angle between the two selected edges.', - "video_url": "https://imgur.com/oQP6Cv8" + "label": "Edge Angle", + "name": "MN_topo_edge_angle", + "description": ' Calculate the angle between two edges, selected with the edge indices. For molecule bonds, combinations of [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] will select all possible bond angles.\n\nIn the video example, two edges are selected with their "Edge Index" values. Those atoms which aren\'t valid return false and do not get instanced. The two edge vectors are used to calculate the perpendicular vector through cross product, around which the rotation for the cone is rotated. This demonstrates the ability to calculate the edge angle between the two selected edges.', + "video_url": "https://imgur.com/oQP6Cv8", }, { - 'label': 'Connected Points for Edge Point', - 'name': 'MN_topo_edge_connected_points', - 'description': 'Finds the conntected point for the selected "Edge Index", and returns each point index for all of the points connected to that point. If the connection doesn\'t exist, or the connection is back to the original point, -1 is returned.\n\nIn the video example, a new point is selected based on the "Edge Index". At that point, all of the connecting points are exposed as indices `0, 1, 2, 3`. If that index is not a valid point or connection, or the point is the same as the original point that is being evaluated, then -1 is returned. \n\nThis is one of the more complicated topology nodes, but allows indexing of the atoms that are bonded to a bonded atom. This helps with doing calculations for planar molecules.', - 'video_url': 'https://imgur.com/fZ6srIS', + "label": "Connected Points for Edge Point", + "name": "MN_topo_edge_connected_points", + "description": 'Finds the conntected point for the selected "Edge Index", and returns each point index for all of the points connected to that point. If the connection doesn\'t exist, or the connection is back to the original point, -1 is returned.\n\nIn the video example, a new point is selected based on the "Edge Index". At that point, all of the connecting points are exposed as indices `0, 1, 2, 3`. If that index is not a valid point or connection, or the point is the same as the original point that is being evaluated, then -1 is returned. \n\nThis is one of the more complicated topology nodes, but allows indexing of the atoms that are bonded to a bonded atom. This helps with doing calculations for planar molecules.', + "video_url": "https://imgur.com/fZ6srIS", }, "break", { - 'label': 'Backbone Positions', - 'name': 'MN_topo_backbone', - 'description': 'If the atoms have been through the "Compute Backbone" node, then the backbone atom positions will be available as attributes through this node.\n\nIn the video example, the `Alpha Carbons` output is styled as spheres, where the position is mixed with some of the backbone posiitons. The backbone positions can also be selected from the AA residue higher or lower with the specified offset.', - 'video_url': 'https://imgur.com/6X2wnpY' + "label": "Backbone Positions", + "name": "MN_topo_backbone", + "description": 'If the atoms have been through the "Compute Backbone" node, then the backbone atom positions will be available as attributes through this node.\n\nIn the video example, the `Alpha Carbons` output is styled as spheres, where the position is mixed with some of the backbone posiitons. The backbone positions can also be selected from the AA residue higher or lower with the specified offset.', + "video_url": "https://imgur.com/6X2wnpY", }, { - 'label': 'Compute Backbone', - 'name': 'MN_topo_compute_backbone', - 'description': 'Gets the backbone positions for each AA residue and stores them as attributes, and additionally computes the phi and psi angles for each residue in radians.\n\nIn the video example, the Phi and Psi angles are mapped from (-Pi, Pi) to (0, 1), which is used in the Color Ramp node to choose colors. This is computed on the alpha carbons, but can be used on any of the resulting atoms for the corresponding residues, which is shown in the second video.', - 'video_url': ['https://imgur.com/9DNzngY', 'https://imgur.com/W3P9l10'] + "label": "Compute Backbone", + "name": "MN_topo_compute_backbone", + "description": "Gets the backbone positions for each AA residue and stores them as attributes, and additionally computes the phi and psi angles for each residue in radians.\n\nIn the video example, the Phi and Psi angles are mapped from (-Pi, Pi) to (0, 1), which is used in the Color Ramp node to choose colors. This is computed on the alpha carbons, but can be used on any of the resulting atoms for the corresponding residues, which is shown in the second video.", + "video_url": ["https://imgur.com/9DNzngY", "https://imgur.com/W3P9l10"], }, "break", { - 'label': '3-Point Angle', - 'name': 'MN_topo_angle_3point', - 'description': 'Calculate the angle between 3 different points. These points are selected based on their index in the point domain, with Index B being the centre of the calculation.\n\nIn the video example, the same calculation that is occurring internally inside of the `MN_topo_edge_angle` node, is being handled explicity by this node. If the `Index` is being used as `Index B` then the current point that is being evaluated is the centre of the angle calculation. If this value is changed, then the point at the corresponding index is used, which results in a smaller angle in the example video.', - 'video_url': 'https://imgur.com/qXyy2ln' + "label": "3-Point Angle", + "name": "MN_topo_angle_3point", + "description": "Calculate the angle between 3 different points. These points are selected based on their index in the point domain, with Index B being the centre of the calculation.\n\nIn the video example, the same calculation that is occurring internally inside of the `MN_topo_edge_angle` node, is being handled explicity by this node. If the `Index` is being used as `Index B` then the current point that is being evaluated is the centre of the angle calculation. If this value is changed, then the point at the corresponding index is used, which results in a smaller angle in the example video.", + "video_url": "https://imgur.com/qXyy2ln", }, { - 'label': '2-Point Angle', - 'name': 'MN_topo_angle_2point', - 'description': 'Calculate the angle that two points make, relative to the current point being evaluated. Points are selected based on their index, with the centre of the angle calculation being the current point\'s position. Equivalent to using 3-Point angle and using `Index` as the `Index B`.\n\nIn the example video, the angle calculation is similar to that of the 3-Point Angle node, but the middle point is always the current point.', - 'video_url': 'https://imgur.com/xp7Vbaj' + "label": "2-Point Angle", + "name": "MN_topo_angle_2point", + "description": "Calculate the angle that two points make, relative to the current point being evaluated. Points are selected based on their index, with the centre of the angle calculation being the current point's position. Equivalent to using 3-Point angle and using `Index` as the `Index B`.\n\nIn the example video, the angle calculation is similar to that of the 3-Point Angle node, but the middle point is always the current point.", + "video_url": "https://imgur.com/xp7Vbaj", }, { - 'label': 'Point Distance', - 'name': 'MN_topo_point_distance', - 'description': 'Calculate the distance and the vector between the evaluating point and the point selected via the Index.\n\nIn the example video, each point is calculating a vector and a distance between itself and the indexed point. When the Point Mask node is used, this index is then on a per-group basis, so each point in the group points to just the group\'s corresponding point.', - 'video_url': 'https://imgur.com/AykNvDz' + "label": "Point Distance", + "name": "MN_topo_point_distance", + "description": "Calculate the distance and the vector between the evaluating point and the point selected via the Index.\n\nIn the example video, each point is calculating a vector and a distance between itself and the indexed point. When the Point Mask node is used, this index is then on a per-group basis, so each point in the group points to just the group's corresponding point.", + "video_url": "https://imgur.com/AykNvDz", }, "break", { - 'label': 'Group Point Mask', - 'name': 'MN_topo_point_mask', - 'description': 'Returns the index for the atom for each unique group (from res_id) for each point in that group. Allows for example, all atoms in a group to be rotated around the position of the selected atom.\n\nIn the video example, the `atom_name` is used to select an atom within the groups. Each atom\'s position is then offset to that position, showing the group-wise selection.', - 'video_url': 'https://imgur.com/sD3jRTR' + "label": "Group Point Mask", + "name": "MN_topo_point_mask", + "description": "Returns the index for the atom for each unique group (from res_id) for each point in that group. Allows for example, all atoms in a group to be rotated around the position of the selected atom.\n\nIn the video example, the `atom_name` is used to select an atom within the groups. Each atom's position is then offset to that position, showing the group-wise selection.", + "video_url": "https://imgur.com/sD3jRTR", }, ], - - 'assembly': [ + "assembly": [ { - 'label': 'Biological Assembly', - 'name': 'mn.assembly_bio', - 'backup': 'MN_assembly_', + "label": "Biological Assembly", + "name": "mn.assembly_bio", + "backup": "MN_assembly_", "description": "Creates a biological assembly by applying rotation and translation matrices to individual chains in the structure. It is created on an individual molecule basis, if assembly instructions are detected when imported.", - "video_url": "https://imgur.com/6jyAP1z" + "video_url": "https://imgur.com/6jyAP1z", }, { - 'label': 'Center Assembly', - 'name': 'MN_assembly_center', + "label": "Center Assembly", + "name": "MN_assembly_center", "description": "Move an instanced assembly to the world origin. Some structures are not centred on the world origin, so this node can reset them to the world origin for convenient rotation and translation and animation.", - "video_url": "https://imgur.com/pgFTmgC" - } + "video_url": "https://imgur.com/pgFTmgC", + }, ], - - - 'DNA': [ + "DNA": [ { - 'label': 'Double Helix', - 'name': 'MN_dna_double_helix', - 'description': "Create a DNA double helix from an input curve.\nTakes an input curve and instances for the bases, returns instances of the bases in a double helix formation" + "label": "Double Helix", + "name": "MN_dna_double_helix", + "description": "Create a DNA double helix from an input curve.\nTakes an input curve and instances for the bases, returns instances of the bases in a double helix formation", }, { - 'label': 'Bases', - 'name': 'MN_dna_bases', - 'description': "Provide the DNA bases as instances to be styled and passed onto the Double Helix node" + "label": "Bases", + "name": "MN_dna_bases", + "description": "Provide the DNA bases as instances to be styled and passed onto the Double Helix node", }, "break", { - 'label': 'Style Spheres Cycles', - 'name': 'MN_dna_style_spheres_cycles', - 'description': "Style the DNA bases with spheres only visible in Cycles" + "label": "Style Spheres Cycles", + "name": "MN_dna_style_spheres_cycles", + "description": "Style the DNA bases with spheres only visible in Cycles", }, { - 'label': 'Style Spheres EEVEE', - 'name': 'MN_dna_style_spheres_eevee', - 'description': "Style the DNA bases with spheres visible in Cycles and EEVEE" + "label": "Style Spheres EEVEE", + "name": "MN_dna_style_spheres_eevee", + "description": "Style the DNA bases with spheres visible in Cycles and EEVEE", }, { - 'label': 'Style Surface', - 'name': 'MN_dna_style_surface', - 'description': "Style the DNA bases with surface representation" + "label": "Style Surface", + "name": "MN_dna_style_surface", + "description": "Style the DNA bases with surface representation", }, { - 'label': 'Style Ball and Stick', - 'name': 'MN_dna_style_ball_and_stick', - 'description': "Style the DNA bases with ball and stick representation" - } + "label": "Style Ball and Stick", + "name": "MN_dna_style_ball_and_stick", + "description": "Style the DNA bases with ball and stick representation", + }, ], - - 'animate': [ + "animate": [ { - 'label': 'Animate Frames', - 'name': 'MN_animate_frames', + "label": "Animate Frames", + "name": "MN_animate_frames", "description": "Animate the atoms of a structure, based on the frames of a trajectory from the `Frames` collection in the input. The structure animates through the trajectory from the given start frame to the given end frame, as the `Animate 0..1` value moves from `0` to `1`. Values higher than `1` start at the beginning again and the trajectory will loop repeating every `1.00`.\nPosition and `b_factor` are interpolated if available. By default linear interpolation is used. Smoothing in and out of each frame can be applied with the `Smoother Step`, or no interpolation at all.", - "video_url": "https://imgur.com/m3BPUxh" + "video_url": "https://imgur.com/m3BPUxh", }, { - 'label': 'Animate Value', - 'name': 'MN_animate_value', + "label": "Animate Value", + "name": "MN_animate_value", "description": "Animate a float value between the specified min and max values, over specified range of frames. If clamped, frames above and below the start and end will result in the min and max output values, otherwise it will continue to linearly interpolate the value beyond the min and max values.", - "video_url": "https://imgur.com/2oOnwRm" + "video_url": "https://imgur.com/2oOnwRm", }, "break", { - 'label': 'Res Wiggle', - 'name': 'MN_animate_res_wiggle', + "label": "Res Wiggle", + "name": "MN_animate_res_wiggle", "description": "Create a procedural animation of side-chain movement. 'Wiggles' the side-chains of peptide amino acids based on the `b_factor` attribute. Wiggle is currently only supported for protein side-chains and does not check for steric clashing so higher amplitudes will result in strange results. The animation should seamlessly loop every `1.00` of the `Animate 0..1` input.", - "video_url": "https://imgur.com/GK1nyUz" + "video_url": "https://imgur.com/GK1nyUz", }, { - 'label': 'Res to Curve', - 'name': 'MN_animate_res_to_curve', + "label": "Res to Curve", + "name": "MN_animate_res_to_curve", "description": "Take the protein residues from a structure and align then along an input curve. Editing the curve will change how the atoms are arranged. The output atoms can be styled as normal.", - "video_url": "https://imgur.com/FcEXSZx" + "video_url": "https://imgur.com/FcEXSZx", }, "break", { - 'label': 'Noise Position', - 'name': 'MN_animate_noise_position', + "label": "Noise Position", + "name": "MN_animate_noise_position", "description": "Create 3D noise vector based on the position of points in 3D space. Evolve the noise function with the `Animate` input, and change the characteristics of the noise function with the other inputs such as scale and detail. There is also a 1-dimensional noise output called `Fac`.\n\nAn example of using this noise is to offset the positions of atoms with the `Set Position` node.", - "video_url": "https://imgur.com/B8frW1C" + "video_url": "https://imgur.com/B8frW1C", }, { - 'label': 'Noise Field', - 'name': 'MN_animate_noise_field', + "label": "Noise Field", + "name": "MN_animate_noise_field", "description": "Create a 3D noise vector based on the input field. Evolve the noise function with the `Animate` input, and change the characteristics of the noise function with the other inputs such as scale and detail. There is also a 1-dimensional noise output called `Fac`.\n\nAn example of using this noise is to offset the positions of atoms with the `Set Position` node. Different field inputs result in different noise being applied. Using the `chain_id` results in the same noise being generated for each atom in each chain, but different between chains.", - "video_url": "https://imgur.com/hqemVQy" + "video_url": "https://imgur.com/hqemVQy", }, { - 'label': 'Noise Repeat', - 'name': 'MN_animate_noise_repeat', + "label": "Noise Repeat", + "name": "MN_animate_noise_repeat", "description": "Create a 3D noise vector based on the input field, that repeats every `1.00` for the `Animate 0..1` input. Evolve the noise function with the `Animate` input, and change the characteristics of the noise function with the other inputs such as scale and detail. There is also a 1-dimensional noise output called `Fac`.\n\nAn example of using this noise is to offset the positions of atoms with the `Set Position` node. Different field inputs result in different noise being applied. Using the `chain_id` results in the same noise being generated for each atom in each chain, but different between chains.", - "video_url": "https://imgur.com/GNQcIlx" - } + "video_url": "https://imgur.com/GNQcIlx", + }, ], - - 'utils': [ + "utils": [ { - 'label': 'Curve Resample', - 'name': 'MN_utils_curve_resample', - 'description': '' + "label": "Curve Resample", + "name": "MN_utils_curve_resample", + "description": "", }, { - 'label': 'Vector Angle', - 'name': 'MN_utils_vector_angle', - 'description': 'Compute the angle in radians between two vectors.' + "label": "Vector Angle", + "name": "MN_utils_vector_angle", + "description": "Compute the angle in radians between two vectors.", }, { - 'label': 'Vector Axis Angle', - 'name': 'MN_utils_vector_angle_axis', - 'description': 'Computes the angle between two vectors, AB & CD around around the axis of BC. The first vector AB is treated as the "12 O\'clock" up position, looking down the axis towards C, with angles being return in the range of (-Pi, Pi). Clockwise angles are positive and anti-clockwise angles are negative.', - 'video_url': '' + "label": "Vector Axis Angle", + "name": "MN_utils_vector_angle_axis", + "description": 'Computes the angle between two vectors, AB & CD around around the axis of BC. The first vector AB is treated as the "12 O\'clock" up position, looking down the axis towards C, with angles being return in the range of (-Pi, Pi). Clockwise angles are positive and anti-clockwise angles are negative.', + "video_url": "", }, # { # 'label': 'Determine Secondary Structure', @@ -494,48 +480,42 @@ # 'description': '' # }, { - 'label': 'Cartoon Utilities', - 'name': '.MN_utils_style_cartoon', - 'description': 'The underlying node group which powers the cartoon style' + "label": "Cartoon Utilities", + "name": ".MN_utils_style_cartoon", + "description": "The underlying node group which powers the cartoon style", }, { - 'label': 'Spheres Cycles', - 'name': '.MN_utils_style_spheres_cycles', - 'description': 'A sphere atom representation, visible ONLY in Cycles. Based on point-cloud rendering' + "label": "Spheres Cycles", + "name": ".MN_utils_style_spheres_cycles", + "description": "A sphere atom representation, visible ONLY in Cycles. Based on point-cloud rendering", }, { - 'label': 'Spheres EEVEE', - 'name': '.MN_utils_style_spheres_eevee', - 'description': 'A sphere atom representation, visible in EEVEE and Cycles. Based on mesh instancing which slows down viewport performance' - } + "label": "Spheres EEVEE", + "name": ".MN_utils_style_spheres_eevee", + "description": "A sphere atom representation, visible in EEVEE and Cycles. Based on mesh instancing which slows down viewport performance", + }, ], - - 'cellpack': [ - { - 'label': 'Pack Instances', - 'name': 'MN_pack_instances', - 'description': '' - } + "cellpack": [ + {"label": "Pack Instances", "name": "MN_pack_instances", "description": ""} ], - - 'density': [ + "density": [ { - 'label': 'Style Surface', - 'name': 'MN_density_style_surface', + "label": "Style Surface", + "name": "MN_density_style_surface", "description": "A surface made from the electron density given a certain threshold value.", - "video_url": "https://imgur.com/jGgMSd4" + "video_url": "https://imgur.com/jGgMSd4", }, { - 'label': 'Style Wire', - 'name': 'MN_density_style_wire', + "label": "Style Wire", + "name": "MN_density_style_wire", "description": "A wire surface made from the electron density given a certain threshold value.", - "video_url": "https://imgur.com/jGgMSd4" + "video_url": "https://imgur.com/jGgMSd4", }, { - 'label': 'Sample Nearest Attribute', - 'name': 'MN_density_sample_nearest', + "label": "Sample Nearest Attribute", + "name": "MN_density_sample_nearest", "description": "Sample the nearest atoms from another object, to get the colors or other attributes and apply them to a volume mesh.", - "video_url": "https://imgur.com/UzNwLv2" - } - ] + "video_url": "https://imgur.com/UzNwLv2", + }, + ], } diff --git a/molecularnodes/ui/node_menu.py b/molecularnodes/ui/node_menu.py index 6d330295..f07b2e62 100644 --- a/molecularnodes/ui/node_menu.py +++ b/molecularnodes/ui/node_menu.py @@ -5,106 +5,106 @@ class MN_MT_Node_Color(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_COLOR' - bl_label = '' + bl_idname = "MN_MT_NODE_COLOR" + bl_label = "" def draw(self, context): layout = self.layout - build_menu(layout, menu_items['color']) + build_menu(layout, menu_items["color"]) class MN_MT_Node_Bonds(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_BONDS' - bl_label = '' + bl_idname = "MN_MT_NODE_BONDS" + bl_label = "" def draw(self, context): layout = self.layout - build_menu(layout, menu_items['bonds']) + build_menu(layout, menu_items["bonds"]) class MN_MT_Node_Style(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_STYLE' - bl_label = '' + bl_idname = "MN_MT_NODE_STYLE" + bl_label = "" def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - build_menu(layout, menu_items['style']) + build_menu(layout, menu_items["style"]) class MN_MT_Node_Select(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_SELECT' - bl_label = '' + bl_idname = "MN_MT_NODE_SELECT" + bl_label = "" def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - build_menu(layout, menu_items['select']) + build_menu(layout, menu_items["select"]) class MN_MT_Node_Assembly(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_ASSEMBLY' - bl_label = '' + bl_idname = "MN_MT_NODE_ASSEMBLY" + bl_label = "" def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - build_menu(layout, menu_items['assembly']) + build_menu(layout, menu_items["assembly"]) class MN_MT_Node_DNA(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_DNA' - bl_label = '' + bl_idname = "MN_MT_NODE_DNA" + bl_label = "" def draw(self, context): layout = self.layout - build_menu(layout, menu_items['DNA']) + build_menu(layout, menu_items["DNA"]) class MN_MT_Node_Animate(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_ANIMATE' - bl_label = '' + bl_idname = "MN_MT_NODE_ANIMATE" + bl_label = "" def draw(self, context): layout = self.layout - build_menu(layout, menu_items['animate']) + build_menu(layout, menu_items["animate"]) class MN_MT_Node_Utils(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_UTILS' - bl_label = '' + bl_idname = "MN_MT_NODE_UTILS" + bl_label = "" def draw(self, context): - build_menu(self.layout, menu_items['utils']) + build_menu(self.layout, menu_items["utils"]) class MN_MT_Node_CellPack(bpy.types.Menu): bl_idname = "MN_MT_NODE_CELLPACK" - bl_label = '' + bl_label = "" def draw(self, context): layout = self.layout - build_menu(layout, menu_items['cellpack']) + build_menu(layout, menu_items["cellpack"]) class MN_MT_Node_Density(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_DENSITY' - bl_label = '' + bl_idname = "MN_MT_NODE_DENSITY" + bl_label = "" def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - build_menu(layout, menu_items['density']) + build_menu(layout, menu_items["density"]) class MN_MT_Node_Topology(bpy.types.Menu): - bl_idname = 'MN_MT_NODE_TOPOLOGY' - bl_label = '' + bl_idname = "MN_MT_NODE_TOPOLOGY" + bl_label = "" def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - build_menu(layout, menu_items['topology']) + build_menu(layout, menu_items["topology"]) class MN_MT_Node(bpy.types.Menu): @@ -115,23 +115,19 @@ def draw(self, context): layout = self.layout.column_flow(columns=1) layout.operator_context = "INVOKE_DEFAULT" - layout.menu('MN_MT_NODE_STYLE', text='Style', icon_value=77) - layout.menu('MN_MT_NODE_SELECT', text='Selection', icon_value=256) - layout.menu('MN_MT_NODE_COLOR', text='Color', icon='COLORSET_07_VEC') - layout.menu('MN_MT_NODE_ANIMATE', text='Animation', icon_value=409) - layout.menu('MN_MT_NODE_TOPOLOGY', text='Topology', - icon='ORIENTATION_CURSOR') - layout.menu('MN_MT_NODE_ASSEMBLY', - text='Assemblies', icon='GROUP_VERTEX') - layout.menu('MN_MT_NODE_CELLPACK', - text='CellPack', icon='PARTICLE_POINT') - layout.menu('MN_MT_NODE_DENSITY', text='Density', icon="VOLUME_DATA") - layout.menu('MN_MT_NODE_DNA', text='DNA', - icon='GP_SELECT_BETWEEN_STROKES') - layout.menu('MN_MT_NODE_UTILS', text='Utilities', icon_value=92) + layout.menu("MN_MT_NODE_STYLE", text="Style", icon_value=77) + layout.menu("MN_MT_NODE_SELECT", text="Selection", icon_value=256) + layout.menu("MN_MT_NODE_COLOR", text="Color", icon="COLORSET_07_VEC") + layout.menu("MN_MT_NODE_ANIMATE", text="Animation", icon_value=409) + layout.menu("MN_MT_NODE_TOPOLOGY", text="Topology", icon="ORIENTATION_CURSOR") + layout.menu("MN_MT_NODE_ASSEMBLY", text="Assemblies", icon="GROUP_VERTEX") + layout.menu("MN_MT_NODE_CELLPACK", text="CellPack", icon="PARTICLE_POINT") + layout.menu("MN_MT_NODE_DENSITY", text="Density", icon="VOLUME_DATA") + layout.menu("MN_MT_NODE_DNA", text="DNA", icon="GP_SELECT_BETWEEN_STROKES") + layout.menu("MN_MT_NODE_UTILS", text="Utilities", icon_value=92) def MN_add_node_menu(self, context): - if ('GeometryNodeTree' == bpy.context.area.spaces[0].tree_type): + if "GeometryNodeTree" == bpy.context.area.spaces[0].tree_type: layout = self.layout - layout.menu('MN_MT_NODE', text='Molecular Nodes', icon_value=88) + layout.menu("MN_MT_NODE", text="Molecular Nodes", icon_value=88) diff --git a/molecularnodes/ui/ops.py b/molecularnodes/ui/ops.py index 9fe943f6..6f941f85 100644 --- a/molecularnodes/ui/ops.py +++ b/molecularnodes/ui/ops.py @@ -8,20 +8,16 @@ class MN_OT_Add_Custom_Node_Group(bpy.types.Operator): # bl_description = "Add Molecular Nodes custom node group." bl_options = {"REGISTER", "UNDO"} node_name: bpy.props.StringProperty( - name='node_name', - description='', - default='', - subtype='NONE', - maxlen=0 + name="node_name", description="", default="", subtype="NONE", maxlen=0 ) - node_label: bpy.props.StringProperty(name='node_label', default='') + node_label: bpy.props.StringProperty(name="node_label", default="") node_description: bpy.props.StringProperty( name="node_description", description="", default="Add MolecularNodes custom node group.", - subtype="NONE" + subtype="NONE", ) - node_link: bpy.props.BoolProperty(name='node_link', default=True) + node_link: bpy.props.BoolProperty(name="node_link", default=True) @classmethod def description(cls, context, properties): @@ -32,8 +28,10 @@ def execute(self, context): nodes.append(self.node_name, link=self.node_link) nodes.add_node(self.node_name) # , label=self.node_label) except RuntimeError: - self.report({'ERROR'}, - message='Failed to add node. Ensure you are not in edit mode.') + self.report( + {"ERROR"}, + message="Failed to add node. Ensure you are not in edit mode.", + ) return {"FINISHED"} @@ -49,7 +47,7 @@ class MN_OT_Assembly_Bio(bpy.types.Operator): @classmethod def poll(self, context): mol = context.active_object - return mol.mn['molecule_type'] in ['pdb', 'local'] + return mol.mn["molecule_type"] in ["pdb", "local"] def execute(self, context): tree_assembly = nodes.assembly_initialise(context.active_object) @@ -66,8 +64,7 @@ class MN_OT_Color_Custom(bpy.types.Operator): description: bpy.props.StringProperty(name="description", default="") node_name: bpy.props.StringProperty(name="node_name", default="") - node_property: bpy.props.StringProperty( - name="node_property", default="chain_ids") + node_property: bpy.props.StringProperty(name="node_property", default="chain_ids") field: bpy.props.StringProperty(name="field", default="chain_id") prefix: bpy.props.StringProperty(name="prefix", default="Chain") starting_value: bpy.props.IntProperty(name="starting_value", default=0) @@ -81,13 +78,15 @@ def execute(self, context): prop = object[self.node_property] if not prop: self.report( - {"WARNING"}, message=f"{self.node_property} not available for {object.name}.") + {"WARNING"}, + message=f"{self.node_property} not available for {object.name}.", + ) return {"CANCELLED"} node_color = nodes.custom_iswitch( name=f"MN_color_{self.node_name}_{object.name}", iter_list=prop, - dtype='RGBA', + dtype="RGBA", field=self.field, prefix=self.prefix, start=self.starting_value, @@ -106,8 +105,7 @@ class MN_OT_selection_custom(bpy.types.Operator): description: bpy.props.StringProperty(name="Description") field: bpy.props.StringProperty(name="field", default="chain_id") prefix: bpy.props.StringProperty(name="prefix", default="Chain ") - node_property: bpy.props.StringProperty( - name="node_property", default="chain_ids") + node_property: bpy.props.StringProperty(name="node_property", default="chain_ids") node_name: bpy.props.StringProperty(name="node_name", default="chain") starting_value: bpy.props.IntProperty(name="starting_value", default=0) @@ -121,16 +119,18 @@ def execute(self, context): name = object.name if not prop: self.report( - {"WARNING"}, message=f"{self.node_property} not available for {object.name}.") + {"WARNING"}, + message=f"{self.node_property} not available for {object.name}.", + ) return {"CANCELLED"} node_chains = nodes.custom_iswitch( - name=f'MN_select_{self.node_name}_{name}', - dtype='BOOLEAN', + name=f"MN_select_{self.node_name}_{name}", + dtype="BOOLEAN", iter_list=prop, start=self.starting_value, field=self.field, - prefix=self.prefix + prefix=self.prefix, ) nodes.add_node(node_chains.name) @@ -149,12 +149,12 @@ class MN_OT_Residues_Selection_Custom(bpy.types.Operator): input_resid_string: bpy.props.StringProperty( name="Select residue IDs: ", description="Enter a string value.", - default="19,94,1-16" - ) + default="19,94,1-16", + ) # type: ignore def execute(self, context): node_residues = nodes.resid_multiple_selection( - node_name='MN_select_res_id_custom', + node_name="MN_select_res_id_custom", input_resid_string=self.input_resid_string, ) diff --git a/molecularnodes/ui/panel.py b/molecularnodes/ui/panel.py index 3556b3df..79fb4c4c 100644 --- a/molecularnodes/ui/panel.py +++ b/molecularnodes/ui/panel.py @@ -1,68 +1,62 @@ import bpy from .. import pkg from ..blender import nodes -from ..io import ( - wwpdb, local, star, cellpack, md, density, dna -) +from ..io import wwpdb, local, star, cellpack, md, density, dna bpy.types.Scene.MN_panel = bpy.props.EnumProperty( name="Panel Selection", items=( - ('import', "Import", "Import macromolecules", 0), - ('object', "Object", "Adjust settings affecting the selected object", 1), - ('scene', "Scene", "Change settings for the world and rendering", 2) - ) + ("import", "Import", "Import macromolecules", 0), + ("object", "Object", "Adjust settings affecting the selected object", 1), + ("scene", "Scene", "Change settings for the world and rendering", 2), + ), ) bpy.types.Scene.MN_panel_import = bpy.props.EnumProperty( name="Method", items=( - ('pdb', "PDB", "Download from the PDB", 0), - ('local', "Local", "Open a local file", 1), - ('md', "MD", "Import a molecular dynamics trajectory", 2), - ('density', "Density", "Import an EM Density Map", 3), - ('star', 'Starfile', "Import a .starfile mapback file", 4), - ('cellpack', 'CellPack', "Import a CellPack .cif/.bcif file", 5), - ('dna', 'oxDNA', 'Import an oxDNA file', 6) - ) + ("pdb", "PDB", "Download from the PDB", 0), + ("local", "Local", "Open a local file", 1), + ("md", "MD", "Import a molecular dynamics trajectory", 2), + ("density", "Density", "Import an EM Density Map", 3), + ("star", "Starfile", "Import a .starfile mapback file", 4), + ("cellpack", "CellPack", "Import a CellPack .cif/.bcif file", 5), + ("dna", "oxDNA", "Import an oxDNA file", 6), + ), ) chosen_panel = { - 'pdb': wwpdb, - 'local': local, - 'star': star, - 'md': md, - 'density': density, - 'cellpack': cellpack, - 'dna': dna - + "pdb": wwpdb, + "local": local, + "star": star, + "md": md, + "density": density, + "cellpack": cellpack, + "dna": dna, } packages = { - 'pdb': ['biotite'], - 'star': ['starfile','mrcfile','pillow'], - 'local': ['biotite'], - 'cellpack': ['biotite', 'msgpack'], - 'md': ['MDAnalysis'], - 'density': ['mrcfile'], - 'dna': [] + "pdb": ["biotite"], + "star": ["starfile", "mrcfile", "pillow"], + "local": ["biotite"], + "cellpack": ["biotite", "msgpack"], + "md": ["MDAnalysis"], + "density": ["mrcfile"], + "dna": [], } class MN_OT_Change_Style(bpy.types.Operator): - bl_idname = 'mn.style_change' - bl_label = 'Style' + bl_idname = "mn.style_change" + bl_label = "Style" - style: bpy.props.EnumProperty( - name="Style", - items=nodes.STYLE_ITEMS - ) + style: bpy.props.EnumProperty(name="Style", items=nodes.STYLE_ITEMS) def execute(self, context): object = context.active_object nodes.change_style_node(object, self.style) - return {'FINISHED'} + return {"FINISHED"} def check_installs(selection): @@ -76,15 +70,14 @@ def check_installs(selection): def panel_import(layout, context): scene = context.scene selection = scene.MN_panel_import - layout.prop(scene, 'MN_panel_import') + layout.prop(scene, "MN_panel_import") install_required = not check_installs(selection) buttons = layout.column(align=True) if install_required: - buttons.label(text='Please install the requried packages.') + buttons.label(text="Please install the requried packages.") for package in packages[selection]: - pkg.button_install_pkg(buttons, package, pkg.get_pkgs()[ - package]['version']) + pkg.button_install_pkg(buttons, package, pkg.get_pkgs()[package]["version"]) col = layout.column() col.enabled = not install_required @@ -97,7 +90,7 @@ def ui_from_node(layout, node): for user control in a panel, rather than through the node editor. """ col = layout.column(align=True) - ntree = bpy.context.active_object.modifiers['MolecularNodes'].node_group + ntree = bpy.context.active_object.modifiers["MolecularNodes"].node_group tree = node.node_tree.interface.items_tree @@ -124,7 +117,7 @@ def panel_object(layout, context): if mol_type == "pdb": layout.label(text=f"PDB: {object.mn.pdb_code.upper()}") if mol_type == "md": - layout.prop(object.mn, 'subframes') + layout.prop(object.mn, "subframes") if mol_type == "star": layout.label(text=f"Ensemble") box = layout.box() @@ -134,13 +127,14 @@ def panel_object(layout, context): row = layout.row(align=True) row.label(text="Style") current_style = nodes.format_node_name( - nodes.get_style_node(object).node_tree.name).replace("Style ", "") - row.operator_menu_enum('mn.style_change', 'style', text=current_style) + nodes.get_style_node(object).node_tree.name + ).replace("Style ", "") + row.operator_menu_enum("mn.style_change", "style", text=current_style) box = layout.box() ui_from_node(box, nodes.get_style_node(object)) row = layout.row() row.label(text="Experimental", icon_value=2) - row.operator('mn.add_armature') + row.operator("mn.add_armature") def panel_scene(layout, context): @@ -158,10 +152,10 @@ def panel_scene(layout, context): else: world.prop(bpy.data.scenes["Scene"].eevee, "taa_render_samples") world.label(text="Background") - world.prop(world_shader.inputs[1], 'default_value', text='HDRI Strength') + world.prop(world_shader.inputs[1], "default_value", text="HDRI Strength") row = world.row() - row.prop(scene.render, 'film_transparent') - row.prop(world_shader.inputs[2], 'default_value', text="Background") + row.prop(scene.render, "film_transparent") + row.prop(world_shader.inputs[2], "default_value", text="Background") col = grid.column() col.label(text="Camera Settings") @@ -172,39 +166,39 @@ def panel_scene(layout, context): row.prop(bpy.data.scenes["Scene"].render, "resolution_x", text="X") row.prop(bpy.data.scenes["Scene"].render, "resolution_y", text="Y") row = camera.grid_flow() - row.prop(cam.dof, 'use_dof') + row.prop(cam.dof, "use_dof") row.prop(bpy.data.scenes["Scene"].render, "use_motion_blur") focus = camera.column() focus.enabled = cam.dof.use_dof - focus.prop(cam.dof, 'focus_object') + focus.prop(cam.dof, "focus_object") distance = focus.row() - distance.enabled = (cam.dof.focus_object is None) - distance.prop(cam.dof, 'focus_distance') - focus.prop(cam.dof, 'aperture_fstop') + distance.enabled = cam.dof.focus_object is None + distance.prop(cam.dof, "focus_distance") + focus.prop(cam.dof, "aperture_fstop") class MN_PT_panel(bpy.types.Panel): - bl_label = 'Molecular Nodes' - bl_idname = 'MN_PT_panel' - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = 'scene' + bl_label = "Molecular Nodes" + bl_idname = "MN_PT_panel" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" bl_order = 0 - bl_options = {'HEADER_LAYOUT_EXPAND'} + bl_options = {"HEADER_LAYOUT_EXPAND"} bl_ui_units_x = 0 def draw(self, context): layout = self.layout scene = context.scene row = layout.row(align=True) - for p in ['import', 'object', 'scene']: - row.prop_enum(scene, 'MN_panel', p) + for p in ["import", "object", "scene"]: + row.prop_enum(scene, "MN_panel", p) # the possible panel functions to choose between which_panel = { "import": panel_import, "scene": panel_scene, - "object": panel_object + "object": panel_object, } # call the required panel function with the layout and context which_panel[scene.MN_panel](layout, context) diff --git a/molecularnodes/ui/pref.py b/molecularnodes/ui/pref.py index 68079fc9..843f88bf 100644 --- a/molecularnodes/ui/pref.py +++ b/molecularnodes/ui/pref.py @@ -1,17 +1,19 @@ -import bpy import pathlib -from .. import pkg + +import bpy from bpy.types import AddonPreferences +from .. import pkg + install_instructions = "https://bradyajohnston.github.io/MolecularNodes/installation.html#installing-biotite-mdanalysis" ADDON_DIR = pathlib.Path(__file__).resolve().parent.parent bpy.types.Scene.pypi_mirror_provider = bpy.props.StringProperty( - name='pypi_mirror_provider', - description='PyPI Mirror Provider', - options={'TEXTEDIT_UPDATE', 'LIBRARY_EDITABLE'}, - default='Default', - subtype='NONE', + name="pypi_mirror_provider", + description="PyPI Mirror Provider", + options={"TEXTEDIT_UPDATE", "LIBRARY_EDITABLE"}, + default="Default", + subtype="NONE", search=pkg.get_pypi_mirror_alias, ) @@ -20,16 +22,17 @@ class MolecularNodesPreferences(AddonPreferences): - bl_idname = 'molecularnodes' + bl_idname = "molecularnodes" def draw(self, context): layout = self.layout layout.label(text="Install the required packages for MolecularNodes.") - col_main = layout.column(heading='', align=False) + col_main = layout.column(heading="", align=False) row_import = col_main.row() - row_import.prop(bpy.context.scene, - 'pypi_mirror_provider', text='Set PyPI Mirror') + row_import.prop( + bpy.context.scene, "pypi_mirror_provider", text="Set PyPI Mirror" + ) pkgs = pkg.get_pkgs() for package in pkgs.values(): @@ -38,7 +41,7 @@ def draw(self, context): row = col.row() pkg.button_install_pkg( layout=row, - name=package.get('name'), - version=package.get('version'), - desc=package.get('desc') + name=package.get("name"), + version=package.get("version"), + desc=package.get("desc"), ) diff --git a/molecularnodes/utils.py b/molecularnodes/utils.py index e38a24af..dd6e3bca 100644 --- a/molecularnodes/utils.py +++ b/molecularnodes/utils.py @@ -1,10 +1,11 @@ -import bpy -import traceback import os +import traceback import zipfile + +import bpy import numpy as np -from mathutils import Matrix from bpy.app.translations import pgettext_tip as tip_ +from mathutils import Matrix from .ui.pref import ADDON_DIR @@ -53,6 +54,7 @@ def _module_filesystem_remove(path_base, module_name): # The `module_name` is expected to be a result from `_zipfile_root_namelist`. import os import shutil + module_name = os.path.splitext(module_name)[0] for f in os.listdir(path_base): f_base = os.path.splitext(f)[0] @@ -68,6 +70,7 @@ def _zipfile_root_namelist(file_to_extract): # taken from the bpy.ops.preferences.app_template_install() operator source code # Return a list of root paths from zipfile.ZipFile.namelist. import os + root_paths = [] for f in file_to_extract.namelist(): # Python's `zipfile` API always adds a separate at the end of directories. @@ -85,26 +88,28 @@ def _zipfile_root_namelist(file_to_extract): def template_install(): print(os.path.abspath(ADDON_DIR)) - template = os.path.join(os.path.abspath(ADDON_DIR), - 'assets', 'template', 'Molecular Nodes.zip') + template = os.path.join( + os.path.abspath(ADDON_DIR), "assets", "template", "Molecular Nodes.zip" + ) _install_template(template) bpy.utils.refresh_script_paths() def template_uninstall(): import shutil + for folder in bpy.utils.app_template_paths(): - path = os.path.join(os.path.abspath(folder), 'MolecularNodes') + path = os.path.join(os.path.abspath(folder), "MolecularNodes") if os.path.exists(path): shutil.rmtree(path) bpy.utils.refresh_script_paths() -def _install_template(filepath, subfolder='', overwrite=True): +def _install_template(filepath, subfolder="", overwrite=True): # taken from the bpy.ops.preferences.app_template_install() operator source code path_app_templates = bpy.utils.user_resource( - 'SCRIPTS', + "SCRIPTS", path=os.path.join("startup", "bl_app_templates_user", subfolder), create=True, ) @@ -120,10 +125,10 @@ def _install_template(filepath, subfolder='', overwrite=True): # check to see if the file is in compressed format (.zip) if zipfile.is_zipfile(filepath): try: - file_to_extract = zipfile.ZipFile(filepath, 'r') + file_to_extract = zipfile.ZipFile(filepath, "r") except: traceback.print_exc() - return {'CANCELLED'} + return {"CANCELLED"} file_to_extract_root = _zipfile_root_namelist(file_to_extract) if overwrite: @@ -131,22 +136,21 @@ def _install_template(filepath, subfolder='', overwrite=True): _module_filesystem_remove(path_app_templates, f) else: for f in file_to_extract_root: - path_dest = os.path.join( - path_app_templates, os.path.basename(f)) + path_dest = os.path.join(path_app_templates, os.path.basename(f)) if os.path.exists(path_dest): # self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest) - return {'CANCELLED'} + return {"CANCELLED"} try: # extract the file to "bl_app_templates_user" file_to_extract.extractall(path_app_templates) except: traceback.print_exc() - return {'CANCELLED'} + return {"CANCELLED"} else: # Only support installing zipfiles - print('no zipfile') - return {'CANCELLED'} + print("no zipfile") + return {"CANCELLED"} app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old @@ -154,20 +158,21 @@ def _install_template(filepath, subfolder='', overwrite=True): bpy.utils.refresh_script_paths() # print message - msg = ( - tip_("Template Installed (%s) from %r into %r") % - (", ".join(sorted(app_templates_new)), filepath, path_app_templates) + msg = tip_("Template Installed (%s) from %r into %r") % ( + ", ".join(sorted(app_templates_new)), + filepath, + path_app_templates, ) print(msg) # data types for the np.array that will store per-chain symmetry operations dtype = [ - ('assembly_id', int), - ('transform_id', int), - ('chain_id', 'U10'), - ('rotation', float, 4), # quaternion form - ('translation', float, 3) + ("assembly_id", int), + ("transform_id", int), + ("chain_id", "U10"), + ("rotation", float, 4), # quaternion form + ("translation", float, 3), ] @@ -186,11 +191,11 @@ def array_quaternions_from_dict(transforms_dict): matrix = transform[1] arr = np.zeros((len(chains)), dtype=dtype) translation, rotation, scale = Matrix(matrix).decompose() - arr['assembly_id'] = i + 1 - arr['transform_id'] = j - arr['chain_id'] = chains - arr['rotation'] = rotation - arr['translation'] = translation + arr["assembly_id"] = i + 1 + arr["transform_id"] = j + arr["chain_id"] = chains + arr["rotation"] = rotation + arr["translation"] = translation transforms.append(arr) return np.hstack(transforms) diff --git a/tests/constants.py b/tests/constants.py index 7c81f080..24c90363 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,7 +1,31 @@ from pathlib import Path import os -codes = ['4ozs', '8H1B', '1BNA', '8U8W'] -attributes = ['b_factor', 'occupancy', 'vdw_radii', 'lipophobicity', 'charge', 'res_id', 'res_name', 'atomic_number', 'chain_id', 'entity_id', 'atom_id', - 'atom_name', 'sec_struct', 'Color', 'position', 'is_backbone', 'is_alpha_carbon', 'is_solvent', 'is_nucleic', 'is_peptide', 'is_hetero', 'is_carb', 'bond_type','mass'] -data_dir = Path(os.path.abspath(Path(__file__).parent / 'data')) +codes = ["4ozs", "8H1B", "1BNA", "8U8W"] +attributes = [ + "b_factor", + "occupancy", + "vdw_radii", + "lipophobicity", + "charge", + "res_id", + "res_name", + "atomic_number", + "chain_id", + "entity_id", + "atom_id", + "atom_name", + "sec_struct", + "Color", + "position", + "is_backbone", + "is_alpha_carbon", + "is_solvent", + "is_nucleic", + "is_peptide", + "is_hetero", + "is_carb", + "bond_type", + "mass", +] +data_dir = Path(os.path.abspath(Path(__file__).parent / "data")) diff --git a/tests/install.py b/tests/install.py index 0080a40f..d8b84475 100644 --- a/tests/install.py +++ b/tests/install.py @@ -3,23 +3,23 @@ import os import pathlib -REQUIREMENTS = pathlib.Path( - pathlib.Path(__file__).resolve().parent.parent -) / "molecularnodes/requirements.txt" +REQUIREMENTS = ( + pathlib.Path(pathlib.Path(__file__).resolve().parent.parent) + / "molecularnodes/requirements.txt" +) def main(): - python = os.path.realpath(sys.executable) commands = [ - f'{python} -m pip install -r molecularnodes/requirements.txt', + f"{python} -m pip install -r molecularnodes/requirements.txt", # f'{python} -m pip uninstall pytest-snapshot' - f'{python} -m pip install pytest pytest-cov syrupy' + f"{python} -m pip install pytest pytest-cov syrupy", ] for command in commands: - subprocess.run(command.split(' ')) + subprocess.run(command.split(" ")) if __name__ == "__main__": diff --git a/tests/python.py b/tests/python.py index a0b9329e..4e428813 100644 --- a/tests/python.py +++ b/tests/python.py @@ -3,7 +3,7 @@ import os argv = sys.argv -argv = argv[argv.index("--") + 1:] +argv = argv[argv.index("--") + 1 :] def main(): diff --git a/tests/run.py b/tests/run.py index 49695580..2279ad82 100644 --- a/tests/run.py +++ b/tests/run.py @@ -1,13 +1,15 @@ import pytest import sys + argv = sys.argv -argv = argv[argv.index("--") + 1:] +argv = argv[argv.index("--") + 1 :] # run this script like this: # /Applications/Blender.app/Contents/MacOS/Blender -b -P tests/run.py -- . -v # /Applications/Blender.app/Contents/MacOS/Blender -b -P tests/run.py -- . -k test_color_lookup_supplied + def main(): # run the test suite, and we have to manually return the result value if non-zero # value is returned for a failing test diff --git a/tests/test_assembly.py b/tests/test_assembly.py index edd3e7c7..5749a482 100644 --- a/tests/test_assembly.py +++ b/tests/test_assembly.py @@ -11,10 +11,9 @@ DATA_DIR = join(dirname(realpath(__file__)), "data") -@pytest.mark.parametrize("pdb_id, format", itertools.product( - ["1f2n", "5zng"], - ["pdb", "cif"] -)) +@pytest.mark.parametrize( + "pdb_id, format", itertools.product(["1f2n", "5zng"], ["pdb", "cif"]) +) def test_get_transformations(pdb_id, format): """ Compare an assembly built from transformation information in @@ -30,7 +29,9 @@ def test_get_transformations(pdb_id, format): cif_file = biotite_cif.PDBxFile.read(path) atoms = biotite_cif.get_structure( # Make sure `label_asym_id` is used instead of `auth_asym_id` - cif_file, model=1, use_author_fields=False + cif_file, + model=1, + use_author_fields=False, ) ref_assembly = biotite_cif.get_assembly(cif_file, model=1) test_parser = cif.CIFAssemblyParser(cif_file) @@ -43,7 +44,7 @@ def test_get_transformations(pdb_id, format): check_transformations(test_transformations, atoms, ref_assembly) -@pytest.mark.parametrize("assembly_id", [str(i+1) for i in range(5)]) +@pytest.mark.parametrize("assembly_id", [str(i + 1) for i in range(5)]) def test_get_transformations_cif(assembly_id): """ Compare an assembly built from transformation information in @@ -55,11 +56,11 @@ def test_get_transformations_cif(assembly_id): cif_file = biotite_cif.PDBxFile.read(join(DATA_DIR, "1f2n.cif")) atoms = biotite_cif.get_structure( # Make sure `label_asym_id` is used instead of `auth_asym_id` - cif_file, model=1, use_author_fields=False - ) - ref_assembly = biotite_cif.get_assembly( - cif_file, model=1, assembly_id=assembly_id + cif_file, + model=1, + use_author_fields=False, ) + ref_assembly = biotite_cif.get_assembly(cif_file, model=1, assembly_id=assembly_id) test_parser = cif.CIFAssemblyParser(cif_file) test_transformations = test_parser.get_transformations(assembly_id) diff --git a/tests/test_attributes.py b/tests/test_attributes.py index c27bb536..16a75968 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -4,16 +4,12 @@ from .utils import sample_attribute -from .constants import ( - codes, - attributes, - data_dir -) +from .constants import codes, attributes, data_dir mn.unregister() mn.register() -formats = ['pdb', 'cif', 'bcif'] +formats = ["pdb", "cif", "bcif"] @pytest.mark.parametrize("code, format", itertools.product(codes, formats)) diff --git a/tests/test_cellpack.py b/tests/test_cellpack.py index a321c552..aa76f886 100644 --- a/tests/test_cellpack.py +++ b/tests/test_cellpack.py @@ -1,37 +1,30 @@ import molecularnodes as mn import pytest import bpy -from .utils import ( - sample_attribute, - NumpySnapshotExtension -) -from .constants import ( - data_dir -) +from .utils import sample_attribute, NumpySnapshotExtension +from .constants import data_dir mn.unregister() mn.register() -@pytest.mark.parametrize('format', ['bcif', 'cif']) +@pytest.mark.parametrize("format", ["bcif", "cif"]) def test_load_cellpack(snapshot_custom: NumpySnapshotExtension, format): bpy.ops.wm.read_homefile(app_template="") name = f"Cellpack_{format}" ens = mn.io.cellpack.load( - data_dir / f"square1.{format}", - name=name, - node_setup=False, - fraction=0.1 + data_dir / f"square1.{format}", name=name, node_setup=False, fraction=0.1 ) - coll = bpy.data.collections[f'cellpack_{name}'] + coll = bpy.data.collections[f"cellpack_{name}"] instance_names = [object.name for object in coll.objects] assert snapshot_custom == "\n".join(instance_names) assert ens.name == name - ens.modifiers['MolecularNodes'].node_group.nodes['MN_pack_instances'].inputs['As Points'].default_value = False + ens.modifiers["MolecularNodes"].node_group.nodes["MN_pack_instances"].inputs[ + "As Points" + ].default_value = False mn.blender.nodes.realize_instances(ens) for attribute in ens.data.attributes.keys(): - assert snapshot_custom == sample_attribute( - ens, attribute, evaluate=True) - assert snapshot_custom == str(ens['chain_ids']) + assert snapshot_custom == sample_attribute(ens, attribute, evaluate=True) + assert snapshot_custom == str(ens["chain_ids"]) diff --git a/tests/test_color.py b/tests/test_color.py index d481a4c7..0208a257 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -6,10 +6,7 @@ def test_random_rgb(snapshot_custom): n = 100 - colors = np.array(list(map( - lambda x: mn.color.random_rgb(x), - range(n) - ))) + colors = np.array(list(map(lambda x: mn.color.random_rgb(x), range(n)))) assert snapshot_custom == colors diff --git a/tests/test_density.py b/tests/test_density.py index b3bd2fb3..c38ffb99 100644 --- a/tests/test_density.py +++ b/tests/test_density.py @@ -4,10 +4,8 @@ import numpy as np import itertools from .constants import data_dir -from .utils import ( - sample_attribute, - NumpySnapshotExtension -) +from .utils import sample_attribute, NumpySnapshotExtension + try: import pyopenvdb except ImportError: @@ -30,7 +28,6 @@ def density_file(): def test_density_load(density_file): - obj = mn.io.density.load(density_file).object evaluated = mn.blender.obj.evaluate_using_mesh(obj) pos = mn.blender.obj.get_attribute(evaluated, "position") @@ -63,7 +60,6 @@ def test_density_centered(density_file): def test_density_invert(density_file): - # First load using standar parameters to test recreation of vdb o = mn.io.density.load(density_file).object # Then refresh the scene @@ -92,14 +88,14 @@ def test_density_multiple_load(): assert obj2.users_collection[0] == mn.blender.coll.mn() -@pytest.mark.parametrize('name', ['', 'NewDensity']) +@pytest.mark.parametrize("name", ["", "NewDensity"]) def test_density_naming_op(density_file, name): bpy.context.scene.MN_import_density_name = name bpy.context.scene.MN_import_density = str(density_file) bpy.ops.mn.import_density() - if name == '': - object_name = 'emd_24805' + if name == "": + object_name = "emd_24805" else: object_name = name object = bpy.data.objects[object_name] @@ -107,11 +103,11 @@ def test_density_naming_op(density_file, name): assert object.name == object_name -@pytest.mark.parametrize('name', ['', 'NewDensity']) +@pytest.mark.parametrize("name", ["", "NewDensity"]) def test_density_naming_api(density_file, name): object = mn.io.density.load(density_file, name).object - if name == '': - object_name = 'emd_24805' + if name == "": + object_name = "emd_24805" else: object_name = name @@ -119,8 +115,12 @@ def test_density_naming_api(density_file, name): assert object.name == object_name -@pytest.mark.parametrize("invert,node_setup,center", list(itertools.product([True, False], repeat=3))) -def test_density_operator(snapshot_custom: NumpySnapshotExtension, density_file, invert, node_setup, center): +@pytest.mark.parametrize( + "invert,node_setup,center", list(itertools.product([True, False], repeat=3)) +) +def test_density_operator( + snapshot_custom: NumpySnapshotExtension, density_file, invert, node_setup, center +): scene = bpy.context.scene scene.MN_import_density = str(density_file) scene.MN_import_density_invert = invert @@ -133,6 +133,5 @@ def test_density_operator(snapshot_custom: NumpySnapshotExtension, density_file, if bob.name not in bobs: new_bob = bob assert snapshot_custom == sample_attribute( - mn.blender.obj.evaluate_using_mesh(new_bob), - 'position' + mn.blender.obj.evaluate_using_mesh(new_bob), "position" ) diff --git a/tests/test_dna.py b/tests/test_dna.py index 617f5bde..e0a2ed6e 100644 --- a/tests/test_dna.py +++ b/tests/test_dna.py @@ -1,13 +1,8 @@ import numpy as np import molecularnodes as mn from molecularnodes.io import dna -from .utils import ( - sample_attribute, - NumpySnapshotExtension -) -from .constants import ( - data_dir -) +from .utils import sample_attribute, NumpySnapshotExtension +from .constants import data_dir def test_read_topology(): @@ -21,11 +16,7 @@ def test_read_topology(): def test_topology_to_idx(): - top = np.array([ - [1, 31, -1, 1], - [1, 3, 0, 1], - [1, 2, 1, -1] - ]) + top = np.array([[1, 31, -1, 1], [1, 3, 0, 1], [1, 2, 1, -1]]) bonds = dna.toplogy_to_bond_idx_pairs(top) expected = np.array([[0, 1], [1, 2]]) @@ -34,7 +25,7 @@ def test_topology_to_idx(): def test_base_lookup(): - bases = np.array(['A', 'C', 'C', 'G', 'T', '-10', 'G', 'C', '-3']) + bases = np.array(["A", "C", "C", "G", "T", "-10", "G", "C", "-3"]) expected = np.array([30, 31, 31, 32, 33, -1, 32, 31, -1]) ints = dna.base_to_int(bases) @@ -49,11 +40,11 @@ def test_read_trajectory(): def test_read_oxdna(snapshot_custom: NumpySnapshotExtension): - name = 'holliday' + name = "holliday" mol, coll_frames = dna.load( top=data_dir / "oxdna/holliday.top", traj=data_dir / "oxdna/holliday.dat", - name=name + name=name, ) assert len(coll_frames.objects) == 20 diff --git a/tests/test_download.py b/tests/test_download.py index 072c113e..916089f0 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -10,48 +10,54 @@ # currently can't figure out downloading from other services -databases = ['rcsb'] +databases = ["rcsb"] def _filestart(format): if format == "cif": - return 'data_' + return "data_" else: - return 'HEADER' + return "HEADER" def test_download_raises_error_on_invalid_format(): with pytest.raises(ValueError) as excinfo: - download('1abc', 'invalid_format') - assert "File format 'invalid_format' not in: supported_formats=['cif', 'pdb', 'bcif']" in str( - excinfo.value) + download("1abc", "invalid_format") + assert ( + "File format 'invalid_format' not in: supported_formats=['cif', 'pdb', 'bcif']" + in str(excinfo.value) + ) def test_fail_download_pdb_large_structure_raises(): with pytest.raises(FileDownloadPDBError) as excinfo: - download('7D6Z', format='pdb') + download("7D6Z", format="pdb") - assert "There was an error downloading the file from the Protein Data Bank. PDB or format for PDB code may not be available." in str( - excinfo.value + assert ( + "There was an error downloading the file from the Protein Data Bank. PDB or format for PDB code may not be available." + in str(excinfo.value) ) -@pytest.mark.parametrize('format', ['cif', 'bcif', 'pdb']) +@pytest.mark.parametrize("format", ["cif", "bcif", "pdb"]) def test_compare_biotite(format): - struc_download = load_structure(mn.io.download( - '4ozs', format=format, cache=tempfile.TemporaryDirectory().name)) - struc_biotite = load_structure(rcsb.fetch( - '4ozs', format=format, target_path=tempfile.TemporaryDirectory().name)) + struc_download = load_structure( + mn.io.download("4ozs", format=format, cache=tempfile.TemporaryDirectory().name) + ) + struc_biotite = load_structure( + rcsb.fetch( + "4ozs", format=format, target_path=tempfile.TemporaryDirectory().name + ) + ) assert struc_download == struc_biotite -@pytest.mark.parametrize('code', codes) -@pytest.mark.parametrize('database', databases) -@pytest.mark.parametrize('format', ['pdb', 'cif']) +@pytest.mark.parametrize("code", codes) +@pytest.mark.parametrize("database", databases) +@pytest.mark.parametrize("format", ["pdb", "cif"]) def test_fetch_with_cache(tmpdir, code, format, database): cache_dir = tmpdir.mkdir("cache") - file = mn.io.download(code, format, cache=str( - cache_dir), database=database) + file = mn.io.download(code, format, cache=str(cache_dir), database=database) assert isinstance(file, str) assert os.path.isfile(file) @@ -62,12 +68,12 @@ def test_fetch_with_cache(tmpdir, code, format, database): assert content.startswith(_filestart(format)) -databases = ['rcsb'] # currently can't figure out downloading from the pdbe +databases = ["rcsb"] # currently can't figure out downloading from the pdbe -@pytest.mark.parametrize('code', codes) -@pytest.mark.parametrize('database', databases) -@pytest.mark.parametrize('format', ['pdb', 'cif']) +@pytest.mark.parametrize("code", codes) +@pytest.mark.parametrize("database", databases) +@pytest.mark.parametrize("format", ["pdb", "cif"]) def test_fetch_without_cache(tmpdir, code, format, database): file = mn.io.download(code, format, cache=None, database=database) @@ -76,22 +82,21 @@ def test_fetch_without_cache(tmpdir, code, format, database): assert content.startswith(_filestart(format)) -@pytest.mark.parametrize('database', databases) +@pytest.mark.parametrize("database", databases) def test_fetch_with_invalid_format(database): - code = '4OZS' + code = "4OZS" format = "xyz" with pytest.raises(ValueError): mn.io.download(code, format, cache=None, database=database) -@pytest.mark.parametrize('code', codes) -@pytest.mark.parametrize('database', databases) -@pytest.mark.parametrize('format', ['bcif']) +@pytest.mark.parametrize("code", codes) +@pytest.mark.parametrize("database", databases) +@pytest.mark.parametrize("format", ["bcif"]) def test_fetch_with_binary_format(tmpdir, code, database, format): cache_dir = tmpdir.mkdir("cache") - file = mn.io.download(code, format, cache=str( - cache_dir), database=database) + file = mn.io.download(code, format, cache=str(cache_dir), database=database) assert isinstance(file, str) assert os.path.isfile(file) @@ -106,10 +111,10 @@ def test_fetch_with_binary_format(tmpdir, code, database, format): # , 'bcif')) # TODO bcif tests once supported -@pytest.mark.parametrize('format', ('cif', 'pdb')) -@pytest.mark.parametrize('code', ('A0A5E8G9H8', 'A0A5E8G9T8', 'K4PA18')) +@pytest.mark.parametrize("format", ("cif", "pdb")) +@pytest.mark.parametrize("code", ("A0A5E8G9H8", "A0A5E8G9T8", "K4PA18")) def test_alphafold_download(format: str, code: str) -> None: - file = mn.io.download(code=code, format=format, database='alphafold') + file = mn.io.download(code=code, format=format, database="alphafold") if format == "bcif": model = mn.io.parse.BCIF(file) elif format == "cif": diff --git a/tests/test_load.py b/tests/test_load.py index bc7170a5..805632b2 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -3,55 +3,54 @@ import pytest import itertools import molecularnodes as mn -from .constants import ( - data_dir, - codes, - attributes -) +from .constants import data_dir, codes, attributes from .utils import sample_attribute, NumpySnapshotExtension mn.unregister() mn.register() -styles = ['preset_1', 'cartoon', 'ribbon', - 'spheres', 'surface', 'ball_and_stick'] +styles = ["preset_1", "cartoon", "ribbon", "spheres", "surface", "ball_and_stick"] -centre_methods = ['', 'centroid', 'mass'] +centre_methods = ["", "centroid", "mass"] def useful_function(snapshot_custom, style, code, assembly, cache_dir=None): obj = mn.io.fetch( - code, - style=style, - build_assembly=assembly, - cache_dir=cache_dir + code, style=style, build_assembly=assembly, cache_dir=cache_dir ).object node = mn.blender.nodes.get_style_node(obj) - if 'EEVEE' in node.inputs.keys(): - node.inputs['EEVEE'].default_value = True + if "EEVEE" in node.inputs.keys(): + node.inputs["EEVEE"].default_value = True mn.blender.nodes.realize_instances(obj) - dont_realise = style == 'cartoon' and code == '1BNA' + dont_realise = style == "cartoon" and code == "1BNA" for att in attributes: - assert snapshot_custom == sample_attribute( - obj, att, evaluate=dont_realise) + assert snapshot_custom == sample_attribute(obj, att, evaluate=dont_realise) -@pytest.mark.parametrize("assembly, code, style", itertools.product([False], codes, styles)) +@pytest.mark.parametrize( + "assembly, code, style", itertools.product([False], codes, styles) +) def test_style_1(snapshot_custom: NumpySnapshotExtension, assembly, code, style): useful_function(snapshot_custom, style, code, assembly, cache_dir=data_dir) + # have to test a subset of styles with the biological assembly. # testing some of the heavier styles run out of memory and fail on github actions -@pytest.mark.parametrize("assembly, code, style", itertools.product([True], codes, ['cartoon', 'surface', 'ribbon'])) +@pytest.mark.parametrize( + "assembly, code, style", + itertools.product([True], codes, ["cartoon", "surface", "ribbon"]), +) def test_style_2(snapshot_custom: NumpySnapshotExtension, assembly, code, style): useful_function(snapshot_custom, style, code, assembly, cache_dir=data_dir) -@pytest.mark.parametrize("code, format", itertools.product(codes, ['bcif', 'cif', 'pdb'])) +@pytest.mark.parametrize( + "code, format", itertools.product(codes, ["bcif", "cif", "pdb"]) +) def test_download_format(code, format): mol = mn.io.fetch(code, format=format, style=None).object scene = bpy.context.scene @@ -66,29 +65,31 @@ def test_download_format(code, format): mol2 = o def verts(object): - return mn.blender.obj.get_attribute(object, 'position') + return mn.blender.obj.get_attribute(object, "position") assert np.isclose(verts(mol), verts(mol2)).all() -@pytest.mark.parametrize('code, style', itertools.product(codes, styles)) +@pytest.mark.parametrize("code, style", itertools.product(codes, styles)) def test_style_positions(snapshot_custom: NumpySnapshotExtension, code, style): mol = mn.io.fetch(code, style=style).object - assert snapshot_custom == sample_attribute(mol, 'position') + assert snapshot_custom == sample_attribute(mol, "position") -@pytest.mark.parametrize('code, centre_method', itertools.product(codes, centre_methods)) +@pytest.mark.parametrize( + "code, centre_method", itertools.product(codes, centre_methods) +) def test_centring(snapshot_custom: NumpySnapshotExtension, code, centre_method): - """fetch a pdb structure using code and translate the model using the + """fetch a pdb structure using code and translate the model using the centre_method. Check the CoG and CoM values against the snapshot file. """ mol = mn.io.fetch(code, centre=centre_method, cache_dir=data_dir) CoG = mol.centre() - CoM = mol.centre(centre_type='mass') + CoM = mol.centre(centre_type="mass") - if centre_method == 'centroid': + if centre_method == "centroid": assert np.linalg.norm(CoG) < 1e-06 - elif centre_method == 'mass': + elif centre_method == "mass": assert np.linalg.norm(CoM) < 1e-06 CoG = np.array_str(CoG, precision=4, suppress_small=True) @@ -96,58 +97,61 @@ def test_centring(snapshot_custom: NumpySnapshotExtension, code, centre_method): assert snapshot_custom == [CoG, CoM] -@pytest.mark.parametrize('code', codes) +@pytest.mark.parametrize("code", codes) def test_centring_different(code): - """fetch multiple instances of the same pdb structure and translate - each by a different centring method. Check that their centroids and + """fetch multiple instances of the same pdb structure and translate + each by a different centring method. Check that their centroids and positions are in fact different. """ - mols = [mn.io.fetch(code, centre=method, cache_dir=data_dir) - for method in centre_methods] + mols = [ + mn.io.fetch(code, centre=method, cache_dir=data_dir) + for method in centre_methods + ] for mol1, mol2 in itertools.combinations(mols, 2): assert not np.allclose( - mol1.centre(centre_type='centroid'), - mol2.centre(centre_type='centroid') + mol1.centre(centre_type="centroid"), mol2.centre(centre_type="centroid") ) assert not np.allclose( - mol1.centre(centre_type='mass'), - mol2.centre(centre_type='mass') + mol1.centre(centre_type="mass"), mol2.centre(centre_type="mass") ) assert not np.allclose( - mol1.get_attribute('position'), - mol2.get_attribute('position') + mol1.get_attribute("position"), mol2.get_attribute("position") ) # THESE TEST FUNCTIONS ARE NOT RUN def test_local_pdb(snapshot_custom): molecules = [ - mn.io.load(data_dir / f'1l58.{ext}', style='spheres') - for ext in ('cif', 'pdb') + mn.io.load(data_dir / f"1l58.{ext}", style="spheres") for ext in ("cif", "pdb") ] - molecules.append(mn.io.fetch('1l58', format='bcif')) - for att in ['position']: + molecules.append(mn.io.fetch("1l58", format="bcif")) + for att in ["position"]: for mol in molecules: - assert snapshot_custom == sample_attribute( - mol, att, evaluate=False) + assert snapshot_custom == sample_attribute(mol, att, evaluate=False) def test_rcsb_nmr(snapshot_custom): - mol = mn.io.fetch('2M6Q', style='cartoon') + mol = mn.io.fetch("2M6Q", style="cartoon") assert len(mol.frames.objects) == 10 - assert mol.object.modifiers['MolecularNodes'].node_group.nodes['MN_animate_value'].inputs['To Max'].default_value == 9 + assert ( + mol.object.modifiers["MolecularNodes"] + .node_group.nodes["MN_animate_value"] + .inputs["To Max"] + .default_value + == 9 + ) - assert snapshot_custom == sample_attribute(mol, 'position', evaluate=True) + assert snapshot_custom == sample_attribute(mol, "position", evaluate=True) - pos_1 = mol.get_attribute('position', evaluate=True) + pos_1 = mol.get_attribute("position", evaluate=True) bpy.context.scene.frame_set(100) - pos_2 = mol.get_attribute('position', evaluate=True) + pos_2 = mol.get_attribute("position", evaluate=True) assert (pos_1 != pos_2).all() def test_load_small_mol(snapshot_custom): mol = mn.io.load(data_dir / "ASN.cif") - for att in ['position', 'bond_type']: + for att in ["position", "bond_type"]: assert snapshot_custom == sample_attribute(mol, att).tolist() @@ -155,6 +159,7 @@ def test_rcsb_cache(snapshot_custom): from pathlib import Path import tempfile import os + # we want to make sure cached files are freshly downloaded, but # we don't want to delete our entire real cache # Create a temporary directory @@ -162,12 +167,11 @@ def test_rcsb_cache(snapshot_custom): test_cache = Path(data_dir) # Run the test - obj_1 = mn.io.fetch('6BQN', style='cartoon', cache_dir=test_cache) - file = os.path.join(test_cache, '6BQN.bcif') + obj_1 = mn.io.fetch("6BQN", style="cartoon", cache_dir=test_cache) + file = os.path.join(test_cache, "6BQN.bcif") assert os.path.exists(file) - obj_2 = mn.io.fetch('6BQN', style='cartoon', cache_dir=test_cache) + obj_2 = mn.io.fetch("6BQN", style="cartoon", cache_dir=test_cache) assert ( - sample_attribute(obj_1, 'position') == - sample_attribute(obj_2, 'position') + sample_attribute(obj_1, "position") == sample_attribute(obj_2, "position") ).all() diff --git a/tests/test_mda.py b/tests/test_mda.py index 73e5f255..89bee543 100644 --- a/tests/test_mda.py +++ b/tests/test_mda.py @@ -9,14 +9,8 @@ if HAS_mda: import MDAnalysis as mda import numpy as np -from .constants import ( - data_dir -) -from .utils import ( - remove_all_molecule_objects, - sample_attribute, - NumpySnapshotExtension -) +from .constants import data_dir +from .utils import remove_all_molecule_objects, sample_attribute, NumpySnapshotExtension @pytest.mark.skipif(not HAS_mda, reason="MDAnalysis is not installed") @@ -53,11 +47,13 @@ def reload_mda_session(self, mda_session): mda_session_2 = mn.mda.create_session() @pytest.mark.parametrize("in_memory", [False, True]) - def test_show_universe(self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe): + def test_show_universe( + self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe + ): remove_all_molecule_objects(mda_session) mda_session.show(universe, in_memory=in_memory) bob = bpy.data.objects["atoms"] - assert snapshot_custom == sample_attribute(bob, 'position') + assert snapshot_custom == sample_attribute(bob, "position") @pytest.mark.parametrize("in_memory", [False, True]) def test_same_name_atoms(self, in_memory, mda_session, universe): @@ -69,13 +65,15 @@ def test_same_name_atoms(self, in_memory, mda_session, universe): bob_1 = bpy.data.objects["atoms"] bob_2 = bpy.data.objects["atoms.001"] - verts_1 = mn.blender.obj.get_attribute(bob_1, 'position') - verts_2 = mn.blender.obj.get_attribute(bob_2, 'position') + verts_1 = mn.blender.obj.get_attribute(bob_1, "position") + verts_2 = mn.blender.obj.get_attribute(bob_2, "position") assert (verts_1 == verts_2).all() @pytest.mark.parametrize("in_memory", [False, True]) - def test_show_multiple_selection(self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe): + def test_show_multiple_selection( + self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe + ): remove_all_molecule_objects(mda_session) custom_selections = {"name_ca": "name CA"} mda_session.show( @@ -86,19 +84,18 @@ def test_show_multiple_selection(self, snapshot_custom: NumpySnapshotExtension, custom_selections=custom_selections, ) bob = bpy.data.objects["protein"] - assert snapshot_custom == sample_attribute(bob, 'posiiton') + assert snapshot_custom == sample_attribute(bob, "posiiton") # different bahavior in_memory or not. if not in_memory: bob_ca = bpy.data.objects["name_ca"] - assert snapshot_custom == sample_attribute(bob_ca, 'position') + assert snapshot_custom == sample_attribute(bob_ca, "position") else: # attribute is added as name_ca. assert "name_ca" in bob.data.attributes.keys() @pytest.mark.parametrize("in_memory", [False, True]) def test_include_bonds(self, in_memory, mda_session, universe_with_bonds): - remove_all_molecule_objects(mda_session) mda_session.show(universe_with_bonds, in_memory=in_memory) obj = bpy.data.objects["atoms"] @@ -132,28 +129,29 @@ def test_attributes_added(self, in_memory, mda_session, universe): assert att in attributes @pytest.mark.parametrize("in_memory", [False, True]) - def test_trajectory_update(self, snapshot_custom: NumpySnapshotExtension, in_memory, universe): - + def test_trajectory_update( + self, snapshot_custom: NumpySnapshotExtension, in_memory, universe + ): # remove_all_molecule_objects(mda_session) mda_session = mn.io.MDAnalysisSession() - obj = mda_session.show(universe, in_memory=in_memory, style='ribbon') + obj = mda_session.show(universe, in_memory=in_memory, style="ribbon") node = mn.blender.nodes.get_style_node(obj) - group = obj.modifiers['MolecularNodes'].node_group + group = obj.modifiers["MolecularNodes"].node_group if in_memory: - node = group.nodes['MN_animate_value'] - node.inputs['Frame: Start'].default_value = 0 - node.inputs['Frame: End'].default_value = 4 + node = group.nodes["MN_animate_value"] + node.inputs["Frame: Start"].default_value = 0 + node.inputs["Frame: End"].default_value = 4 - if 'EEVEE' in node.inputs.keys(): - node.inputs['EEVEE'].default_value = True + if "EEVEE" in node.inputs.keys(): + node.inputs["EEVEE"].default_value = True if in_memory: mn.blender.nodes.realize_instances(obj) n = 100 - pos_a = sample_attribute(obj, 'position', n=n, evaluate=in_memory) + pos_a = sample_attribute(obj, "position", n=n, evaluate=in_memory) assert snapshot_custom == pos_a # change blender frame to 4 @@ -162,19 +160,21 @@ def test_trajectory_update(self, snapshot_custom: NumpySnapshotExtension, in_mem # if in_memory: # socket.default_value = 250 - pos_b = sample_attribute(obj, 'position', n=n, evaluate=in_memory) + pos_b = sample_attribute(obj, "position", n=n, evaluate=in_memory) assert snapshot_custom == pos_b assert not np.isclose(pos_a, pos_b).all() @pytest.mark.parametrize("in_memory", [False, True]) - def test_show_updated_atoms(self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe): + def test_show_updated_atoms( + self, snapshot_custom: NumpySnapshotExtension, in_memory, mda_session, universe + ): remove_all_molecule_objects(mda_session) updating_ag = universe.select_atoms("around 5 resid 1", updating=True) - mda_session.show(updating_ag, in_memory=in_memory, style='vdw') + mda_session.show(updating_ag, in_memory=in_memory, style="vdw") bob = bpy.data.objects["atoms"] - nodes = bob.modifiers['MolecularNodes'].node_group.nodes + nodes = bob.modifiers["MolecularNodes"].node_group.nodes for node in nodes: for input in node.inputs: if input.name == "Frame: Start": @@ -189,13 +189,13 @@ def test_show_updated_atoms(self, snapshot_custom: NumpySnapshotExtension, in_me mn.blender.nodes.realize_instances(bob) bpy.context.scene.frame_set(0) - verts_frame_0 = get_attribute(bob, 'position', evaluate=True) + verts_frame_0 = get_attribute(bob, "position", evaluate=True) assert snapshot_custom == verts_frame_0 # change blender frame to 1 bpy.context.scene.frame_set(1) # bob = bpy.data.objects["atoms"] - verts_frame_1 = get_attribute(bob, 'position', evaluate=True) + verts_frame_1 = get_attribute(bob, "position", evaluate=True) assert snapshot_custom == verts_frame_1 @@ -218,7 +218,12 @@ def test_update_deleted_objects(self, in_memory, mda_session, universe): @pytest.mark.parametrize("in_memory", [False, True]) def test_save_persistance( - self, snapshot_custom: NumpySnapshotExtension, tmp_path, in_memory, mda_session, universe + self, + snapshot_custom: NumpySnapshotExtension, + tmp_path, + in_memory, + mda_session, + universe, ): remove_all_molecule_objects(mda_session) mda_session.show(universe, in_memory=in_memory) @@ -231,12 +236,12 @@ def test_save_persistance( remove_all_molecule_objects(mda_session) bpy.ops.wm.open_mainfile(filepath=str(tmp_path / "test.blend")) bob = bpy.data.objects["atoms"] - verts_frame_0 = mn.blender.obj.get_attribute(bob, 'position') + verts_frame_0 = mn.blender.obj.get_attribute(bob, "position") # change blender frame to 1 bpy.context.scene.frame_set(1) bob = bpy.data.objects["atoms"] - verts_frame_1 = mn.blender.obj.get_attribute(bob, 'position') + verts_frame_1 = mn.blender.obj.get_attribute(bob, "position") assert snapshot_custom == verts_frame_1 assert not np.isclose(verts_frame_0, verts_frame_1).all() @@ -280,15 +285,15 @@ def test_frame_mapping(self, mda_session, universe): obj = mda_session.show(universe, frame_mapping=[0, 0, 1, 2, 4]) bpy.context.scene.frame_set(0) - verts_a = get_attribute(obj, 'position') + verts_a = get_attribute(obj, "position") bpy.context.scene.frame_set(1) - verts_b = get_attribute(obj, 'position') + verts_b = get_attribute(obj, "position") # test the frame mapping works, that nothing has changed becuase of the mapping assert np.isclose(verts_a, verts_b).all() bpy.context.scene.frame_set(2) - verts_b = get_attribute(obj, 'position') + verts_b = get_attribute(obj, "position") # test that something has now changed assert not np.isclose(verts_a, verts_b).all() @@ -298,45 +303,45 @@ def test_subframes(self, mda_session, universe): obj = bpy.data.objects["atoms"] bpy.context.scene.frame_set(0) - verts_a = get_attribute(obj, 'position') + verts_a = get_attribute(obj, "position") bpy.context.scene.frame_set(1) - verts_b = get_attribute(obj, 'position') + verts_b = get_attribute(obj, "position") # should be no difference because not using subframes assert not np.isclose(verts_a, verts_b).all() for subframes in [1, 2, 3, 4]: frame = 1 fraction = frame % (subframes + 1) / (subframes + 1) - obj.mn['subframes'] = subframes + obj.mn["subframes"] = subframes bpy.context.scene.frame_set(frame) - verts_c = get_attribute(obj, 'position') + verts_c = get_attribute(obj, "position") # now using subframes, there should be a difference assert not np.isclose(verts_b, verts_c).all() - assert np.isclose(verts_c, mn.utils.lerp( - verts_a, verts_b, t=fraction)).all() + assert np.isclose( + verts_c, mn.utils.lerp(verts_a, verts_b, t=fraction) + ).all() def test_subframe_mapping(self, mda_session, universe): remove_all_molecule_objects(mda_session) - mda_session.show(universe, in_memory=False, - frame_mapping=[0, 0, 1, 2, 3]) + mda_session.show(universe, in_memory=False, frame_mapping=[0, 0, 1, 2, 3]) bob = bpy.data.objects["atoms"] bpy.context.scene.frame_set(0) - verts_a = get_attribute(bob, 'position') + verts_a = get_attribute(bob, "position") bpy.context.scene.frame_set(1) - verts_b = get_attribute(bob, 'position') + verts_b = get_attribute(bob, "position") assert np.isclose(verts_a, verts_b).all() bpy.context.scene.frame_set(2) - verts_b = get_attribute(bob, 'position') + verts_b = get_attribute(bob, "position") assert not np.isclose(verts_a, verts_b).all() - bob.mn['subframes'] = 1 + bob.mn["subframes"] = 1 bpy.context.scene.frame_set(3) - verts_c = get_attribute(bob, 'position') + verts_c = get_attribute(bob, "position") assert not np.isclose(verts_b, verts_c).all() assert np.isclose(verts_c, mn.utils.lerp(verts_a, verts_b, 0.5)).all() @@ -347,15 +352,14 @@ def test_martini(snapshot_custom: NumpySnapshotExtension, toplogy): session = mn.io.MDAnalysisSession() remove_all_molecule_objects(session) universe = mda.Universe( - data_dir / "martini" / toplogy, - data_dir / "martini/pent/PENT2_100frames.xtc" + data_dir / "martini" / toplogy, data_dir / "martini/pent/PENT2_100frames.xtc" ) mol = session.show(universe, style="ribbon") - pos_a = sample_attribute(mol, 'position') + pos_a = sample_attribute(mol, "position") bpy.context.scene.frame_set(3) - pos_b = sample_attribute(mol, 'position') + pos_b = sample_attribute(mol, "position") assert not np.isclose(pos_a, pos_b).all() diff --git a/tests/test_mol_sdf.py b/tests/test_mol_sdf.py index 573d3328..319e5a62 100644 --- a/tests/test_mol_sdf.py +++ b/tests/test_mol_sdf.py @@ -10,28 +10,26 @@ mn.register() -formats = ['mol', 'sdf'] +formats = ["mol", "sdf"] @pytest.mark.parametrize("format", formats) def test_open(snapshot_custom, format): - molecule = mn.io.parse.SDF(data_dir / f'caffeine.{format}') + molecule = mn.io.parse.SDF(data_dir / f"caffeine.{format}") assert molecule.array assert molecule.file @pytest.mark.parametrize("format", formats) -@pytest.mark.parametrize("style", ['ball_and_stick', 'spheres', 'surface']) +@pytest.mark.parametrize("style", ["ball_and_stick", "spheres", "surface"]) def test_load(snapshot_custom: NumpySnapshotExtension, format, style): - mol = mn.io.load(data_dir / f'caffeine.{format}', style=style) + mol = mn.io.load(data_dir / f"caffeine.{format}", style=style) assert mol.object - if style == 'spheres': - bl.nodes.get_style_node( - mol.object).inputs['EEVEE'].default_value = True + if style == "spheres": + bl.nodes.get_style_node(mol.object).inputs["EEVEE"].default_value = True mn.blender.nodes.realize_instances(mol.object) for attribute in attributes: - assert snapshot_custom == sample_attribute( - mol, attribute, evaluate=True) + assert snapshot_custom == sample_attribute(mol, attribute, evaluate=True) diff --git a/tests/test_nodes.py b/tests/test_nodes.py index a1d4491c..f10c6a23 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -7,6 +7,7 @@ from .utils import sample_attribute, NumpySnapshotExtension from .constants import codes, data_dir + random.seed(6) mn.unregister() @@ -14,37 +15,44 @@ def test_node_name_format(): - assert mn.blender.nodes.format_node_name( - "MN_style_cartoon") == "Style Cartoon" - assert mn.blender.nodes.format_node_name( - 'MN_dna_double_helix' - ) == 'DNA Double Helix' - assert mn.blender.nodes.format_node_name( - 'MN_topo_vector_angle' - ) == 'Topology Vector Angle' + assert mn.blender.nodes.format_node_name("MN_style_cartoon") == "Style Cartoon" + assert ( + mn.blender.nodes.format_node_name("MN_dna_double_helix") == "DNA Double Helix" + ) + assert ( + mn.blender.nodes.format_node_name("MN_topo_vector_angle") + == "Topology Vector Angle" + ) def test_get_nodes(): - bob = mn.io.fetch('4ozs', style='spheres').object + bob = mn.io.fetch("4ozs", style="spheres").object - assert nodes.get_nodes_last_output(bob.modifiers['MolecularNodes'].node_group)[ - 0].name == "MN_style_spheres" + assert ( + nodes.get_nodes_last_output(bob.modifiers["MolecularNodes"].node_group)[0].name + == "MN_style_spheres" + ) nodes.realize_instances(bob) - assert nodes.get_nodes_last_output(bob.modifiers['MolecularNodes'].node_group)[ - 0].name == "Realize Instances" + assert ( + nodes.get_nodes_last_output(bob.modifiers["MolecularNodes"].node_group)[0].name + == "Realize Instances" + ) assert nodes.get_style_node(bob).name == "MN_style_spheres" - bob2 = mn.io.fetch('1cd3', style='cartoon', build_assembly=True).object + bob2 = mn.io.fetch("1cd3", style="cartoon", build_assembly=True).object - assert nodes.get_nodes_last_output(bob2.modifiers['MolecularNodes'].node_group)[ - 0].name == "MN_assembly_1cd3" + assert ( + nodes.get_nodes_last_output(bob2.modifiers["MolecularNodes"].node_group)[0].name + == "MN_assembly_1cd3" + ) assert nodes.get_style_node(bob2).name == "MN_style_cartoon" def test_selection(): - chain_ids = [let for let in 'ABCDEFG123456'] + chain_ids = [let for let in "ABCDEFG123456"] node = nodes.custom_iswitch( - 'test_node', chain_ids, prefix="Chain ", dtype='BOOLEAN') + "test_node", chain_ids, prefix="Chain ", dtype="BOOLEAN" + ) input_sockets = nodes.inputs(node) for letter, socket in zip(chain_ids, input_sockets.values()): @@ -55,10 +63,9 @@ def test_selection(): @pytest.mark.parametrize("code", codes) @pytest.mark.parametrize("attribute", ["chain_id", "entity_id"]) def test_selection_working(snapshot_custom: NumpySnapshotExtension, attribute, code): - mol = mn.io.fetch(code, style='ribbon', cache_dir=data_dir).object - group = mol.modifiers['MolecularNodes'].node_group - node_sel = nodes.add_selection( - group, mol.name, mol[f'{attribute}s'], attribute) + mol = mn.io.fetch(code, style="ribbon", cache_dir=data_dir).object + group = mol.modifiers["MolecularNodes"].node_group + node_sel = nodes.add_selection(group, mol.name, mol[f"{attribute}s"], attribute) n = len(node_sel.inputs) @@ -67,34 +74,29 @@ def test_selection_working(snapshot_custom: NumpySnapshotExtension, attribute, c nodes.realize_instances(mol) - assert snapshot_custom == sample_attribute(mol, 'position', evaluate=True) + assert snapshot_custom == sample_attribute(mol, "position", evaluate=True) @pytest.mark.parametrize("code", codes) -@pytest.mark.parametrize("attribute", ["chain_id", 'entity_id']) -def test_color_custom(snapshot_custom: NumpySnapshotExtension, code, attribute): - mol = mn.io.fetch(code, style='ribbon', cache_dir=data_dir).object +@pytest.mark.parametrize("attribute", ["chain_id", "entity_id"]) +def test_color_custom(snapshot_custom: NumpySnapshotExtension, code, attribute): + mol = mn.io.fetch(code, style="ribbon", cache_dir=data_dir).object group_col = mn.blender.nodes.custom_iswitch( - name=f'MN_color_entity_{mol.name}', - iter_list=mol[f'{attribute}s'], + name=f"MN_color_entity_{mol.name}", + iter_list=mol[f"{attribute}s"], field=attribute, - dtype='RGBA' - ) - group = mol.modifiers['MolecularNodes'].node_group - node_col = mn.blender.nodes.add_custom( - group, group_col.name, [0, -200]) - group.links.new( - node_col.outputs[0], - group.nodes['MN_color_set'].inputs['Color'] + dtype="RGBA", ) + group = mol.modifiers["MolecularNodes"].node_group + node_col = mn.blender.nodes.add_custom(group, group_col.name, [0, -200]) + group.links.new(node_col.outputs[0], group.nodes["MN_color_set"].inputs["Color"]) - assert snapshot_custom == sample_attribute(mol, 'Color', n=50) + assert snapshot_custom == sample_attribute(mol, "Color", n=50) def test_custom_resid_selection(): - node = mn.blender.nodes.resid_multiple_selection( - 'new_node', '1, 5, 10-20, 40-100') + node = mn.blender.nodes.resid_multiple_selection("new_node", "1, 5, 10-20, 40-100") numbers = [1, 5, 10, 20, 40, 100] assert len(nodes.outputs(node)) == 2 counter = 0 @@ -105,73 +107,64 @@ def test_custom_resid_selection(): def test_op_custom_color(): - mol = mn.io.load(data_dir / '1cd3.cif').object + mol = mn.io.load(data_dir / "1cd3.cif").object mol.select_set(True) group = mn.blender.nodes.custom_iswitch( - name=f'MN_color_chain_{mol.name}', - iter_list=mol['chain_ids'], - dtype='RGBA' + name=f"MN_color_chain_{mol.name}", iter_list=mol["chain_ids"], dtype="RGBA" ) assert group - assert group.interface.items_tree['G'].name == 'G' - assert group.interface.items_tree[-1].name == 'G' - assert group.interface.items_tree[0].name == 'Color' + assert group.interface.items_tree["G"].name == "G" + assert group.interface.items_tree[-1].name == "G" + assert group.interface.items_tree[0].name == "Color" def test_color_lookup_supplied(): col = mn.color.random_rgb(6) - name = 'test' + name = "test" node = mn.blender.nodes.custom_iswitch( name=name, iter_list=range(10, 20), - dtype='RGBA', + dtype="RGBA", default_values=[col for i in range(10)], - start=10 + start=10, ) assert node.name == name for item in nodes.inputs(node).values(): assert np.allclose(np.array(item.default_value), col) node = mn.blender.nodes.custom_iswitch( - name='test2', - iter_list=range(10, 20), - dtype='RGBA', - start=10 + name="test2", iter_list=range(10, 20), dtype="RGBA", start=10 ) for item in nodes.inputs(node).values(): assert not np.allclose(np.array(item.default_value), col) def test_color_chain(snapshot_custom: NumpySnapshotExtension): - mol = mn.io.load(data_dir / '1cd3.cif', style='cartoon').object + mol = mn.io.load(data_dir / "1cd3.cif", style="cartoon").object group_col = mn.blender.nodes.custom_iswitch( - name=f'MN_color_chain_{mol.name}', - iter_list=mol['chain_ids'], - dtype='RGBA' + name=f"MN_color_chain_{mol.name}", iter_list=mol["chain_ids"], dtype="RGBA" ) - group = mol.modifiers['MolecularNodes'].node_group + group = mol.modifiers["MolecularNodes"].node_group node_col = mn.blender.nodes.add_custom(group, group_col.name, [0, -200]) - group.links.new(node_col.outputs[0], - group.nodes['MN_color_set'].inputs['Color']) + group.links.new(node_col.outputs[0], group.nodes["MN_color_set"].inputs["Color"]) - assert snapshot_custom == sample_attribute(mol, 'Color') + assert snapshot_custom == sample_attribute(mol, "Color") def test_color_entity(snapshot_custom: NumpySnapshotExtension): - mol = mn.io.fetch('1cd3', style='cartoon').object + mol = mn.io.fetch("1cd3", style="cartoon").object group_col = mn.blender.nodes.custom_iswitch( - name=f'MN_color_entity_{mol.name}', - iter_list=mol['entity_ids'], - dtype='RGBA', - field='entity_id' + name=f"MN_color_entity_{mol.name}", + iter_list=mol["entity_ids"], + dtype="RGBA", + field="entity_id", ) - group = mol.modifiers['MolecularNodes'].node_group + group = mol.modifiers["MolecularNodes"].node_group node_col = mn.blender.nodes.add_custom(group, group_col.name, [0, -200]) - group.links.new(node_col.outputs[0], - group.nodes['MN_color_set'].inputs['Color']) + group.links.new(node_col.outputs[0], group.nodes["MN_color_set"].inputs["Color"]) - assert snapshot_custom == sample_attribute(mol, 'Color') + assert snapshot_custom == sample_attribute(mol, "Color") def get_links(sockets): @@ -182,69 +175,71 @@ def get_links(sockets): def test_change_style(): - model = mn.io.fetch('1cd3', style='cartoon').object + model = mn.io.fetch("1cd3", style="cartoon").object style_node_1 = nodes.get_style_node(model).name - mn.blender.nodes.change_style_node(model, 'ribbon') + mn.blender.nodes.change_style_node(model, "ribbon") style_node_2 = nodes.get_style_node(model).name assert style_node_1 != style_node_2 - for style in ['ribbon', 'cartoon', 'presets', 'ball_and_stick', 'surface']: + for style in ["ribbon", "cartoon", "presets", "ball_and_stick", "surface"]: style_node_1 = nodes.get_style_node(model) - links_in_1 = [link.from_socket.name for link in get_links( - style_node_1.inputs)] - links_out_1 = [link.from_socket.name for link in get_links( - style_node_1.outputs)] + links_in_1 = [link.from_socket.name for link in get_links(style_node_1.inputs)] + links_out_1 = [ + link.from_socket.name for link in get_links(style_node_1.outputs) + ] nodes.change_style_node(model, style) style_node_2 = nodes.get_style_node(model) - links_in_2 = [link.from_socket.name for link in get_links( - style_node_2.inputs)] - links_out_2 = [link.from_socket.name for link in get_links( - style_node_2.outputs)] + links_in_2 = [link.from_socket.name for link in get_links(style_node_2.inputs)] + links_out_2 = [ + link.from_socket.name for link in get_links(style_node_2.outputs) + ] assert len(links_in_1) == len(links_in_2) assert len(links_out_1) == len(links_out_2) def test_node_topology(snapshot_custom: NumpySnapshotExtension): - mol = mn.io.fetch('1bna', del_solvent=False).object + mol = mn.io.fetch("1bna", del_solvent=False).object group = nodes.get_mod(mol).node_group - group.links.new(group.nodes['Group Input'].outputs[0], - group.nodes['Group Output'].inputs[0]) - node_att = group.nodes.new('GeometryNodeStoreNamedAttribute') - node_att.inputs[2].default_value = 'test_attribute' + group.links.new( + group.nodes["Group Input"].outputs[0], group.nodes["Group Output"].inputs[0] + ) + node_att = group.nodes.new("GeometryNodeStoreNamedAttribute") + node_att.inputs[2].default_value = "test_attribute" nodes.insert_last_node(group, node_att) node_names = [ - node['name'] - for node in mn.ui.node_info.menu_items['topology'] + node["name"] + for node in mn.ui.node_info.menu_items["topology"] if not node == "break" ] for node_name in node_names: # exclude these particular nodes, as they aren't field nodes and so we shouldn't # be testing them here. Will create their own particular tests later - if 'backbone' in node_name or 'bonds' in node_name: + if "backbone" in node_name or "bonds" in node_name: continue - node_topo = nodes.add_custom(group, node_name, - location=[x - 300 for x in node_att.location]) + node_topo = nodes.add_custom( + group, node_name, location=[x - 300 for x in node_att.location] + ) if node_name == "MN_topo_point_mask": - node_topo.inputs['atom_name'].default_value = 61 + node_topo.inputs["atom_name"].default_value = 61 type_to_data_type = { - 'VECTOR': 'FLOAT_VECTOR', - 'VALUE': 'FLOAT', - 'BOOLEAN': 'BOOLEAN', - 'INT': 'INT', - 'RGBA': 'FLOAT_COLOR', - 'ROTATION': 'QUATERNION' + "VECTOR": "FLOAT_VECTOR", + "VALUE": "FLOAT", + "BOOLEAN": "BOOLEAN", + "INT": "INT", + "RGBA": "FLOAT_COLOR", + "ROTATION": "QUATERNION", } for output in node_topo.outputs: node_att.data_type = type_to_data_type[output.type] - input = node_att.inputs['Value'] + input = node_att.inputs["Value"] for link in input.links: group.links.remove(link) @@ -252,42 +247,44 @@ def test_node_topology(snapshot_custom: NumpySnapshotExtension): group.links.new(output, input) assert snapshot_custom == mn.blender.obj.get_attribute( - mol, 'test_attribute', evaluate=True + mol, "test_attribute", evaluate=True ) def test_compute_backbone(snapshot_custom: NumpySnapshotExtension): - mol = mn.io.fetch('1CCN', del_solvent=False).object + mol = mn.io.fetch("1CCN", del_solvent=False).object group = nodes.get_mod(mol).node_group - group.links.new(group.nodes['Group Input'].outputs[0], - group.nodes['Group Output'].inputs[0]) - node_att = group.nodes.new('GeometryNodeStoreNamedAttribute') - node_att.inputs[2].default_value = 'test_attribute' - node_backbone = nodes.add_custom(group, 'MN_topo_compute_backbone') + group.links.new( + group.nodes["Group Input"].outputs[0], group.nodes["Group Output"].inputs[0] + ) + node_att = group.nodes.new("GeometryNodeStoreNamedAttribute") + node_att.inputs[2].default_value = "test_attribute" + node_backbone = nodes.add_custom(group, "MN_topo_compute_backbone") nodes.insert_last_node(group, node_backbone) nodes.insert_last_node(group, node_att) - node_names = ['MN_topo_backbone'] + node_names = ["MN_topo_backbone"] for node_name in node_names: - node_topo = nodes.add_custom(group, node_name, - location=[x - 300 for x in node_att.location]) + node_topo = nodes.add_custom( + group, node_name, location=[x - 300 for x in node_att.location] + ) if node_name == "MN_topo_point_mask": - node_topo.inputs['atom_name'].default_value = 61 + node_topo.inputs["atom_name"].default_value = 61 type_to_data_type = { - 'VECTOR': 'FLOAT_VECTOR', - 'VALUE': 'FLOAT', - 'BOOLEAN': 'BOOLEAN', - 'INT': 'INT', - 'RGBA': 'FLOAT_COLOR', - 'ROTATION': 'QUATERNION' + "VECTOR": "FLOAT_VECTOR", + "VALUE": "FLOAT", + "BOOLEAN": "BOOLEAN", + "INT": "INT", + "RGBA": "FLOAT_COLOR", + "ROTATION": "QUATERNION", } for output in node_topo.outputs: node_att.data_type = type_to_data_type[output.type] - input = node_att.inputs['Value'] + input = node_att.inputs["Value"] for link in input.links: group.links.remove(link) @@ -295,13 +292,13 @@ def test_compute_backbone(snapshot_custom: NumpySnapshotExtension): group.links.new(output, input) assert snapshot_custom == mn.blender.obj.get_attribute( - mol, 'test_attribute', evaluate=True + mol, "test_attribute", evaluate=True ) - for angle in ['Phi', 'Psi']: + for angle in ["Phi", "Psi"]: output = node_backbone.outputs[angle] node_att.data_type = type_to_data_type[output.type] - input = node_att.inputs['Value'] + input = node_att.inputs["Value"] for link in input.links: group.links.remove(link) @@ -309,18 +306,18 @@ def test_compute_backbone(snapshot_custom: NumpySnapshotExtension): group.links.new(output, input) assert snapshot_custom == mn.blender.obj.get_attribute( - mol, 'test_attribute', evaluate=True + mol, "test_attribute", evaluate=True ) def test_topo_bonds(): - mol = mn.io.fetch('1BNA', del_solvent=True, style=None).object + mol = mn.io.fetch("1BNA", del_solvent=True, style=None).object group = nodes.get_mod(mol).node_group = nodes.new_group() # add the node that will break bonds, set the cutoff to 0 - node_break = nodes.add_custom(group, 'MN_topo_bonds_break') + node_break = nodes.add_custom(group, "MN_topo_bonds_break") nodes.insert_last_node(group, node=node_break) - node_break.inputs['Cutoff'].default_value = 0 + node_break.inputs["Cutoff"].default_value = 0 # compare the number of edges before and after deleting them with bonds = mol.data.edges @@ -330,7 +327,7 @@ def test_topo_bonds(): # add the node to find the bonds, and ensure the number of bonds pre and post the nodes # are the same (other attributes will be different, but for now this is good) - node_find = nodes.add_custom(group, 'MN_topo_bonds_find') + node_find = nodes.add_custom(group, "MN_topo_bonds_find") nodes.insert_last_node(group, node=node_find) bonds_new = mn.blender.obj.evaluated(mol).data.edges assert len(bonds) == len(bonds_new) diff --git a/tests/test_obj.py b/tests/test_obj.py index 2d9f40a5..b65dc37a 100644 --- a/tests/test_obj.py +++ b/tests/test_obj.py @@ -18,16 +18,13 @@ def test_creat_obj(): def test_set_position(): - mol = mn.io.fetch('8FAT') + mol = mn.io.fetch("8FAT") - pos_a = mol.get_attribute('position') + pos_a = mol.get_attribute("position") - mol.set_attribute( - data=mol.get_attribute('position') + 10, - name='position' - ) + mol.set_attribute(data=mol.get_attribute("position") + 10, name="position") - pos_b = mol.get_attribute('position') + pos_b = mol.get_attribute("position") print(f"{pos_a=}") print(f"{pos_b=}") diff --git a/tests/test_ops.py b/tests/test_ops.py index cdc5fede..66a33c87 100644 --- a/tests/test_ops.py +++ b/tests/test_ops.py @@ -5,11 +5,7 @@ from molecularnodes.blender.obj import ObjectTracker from .utils import sample_attribute, NumpySnapshotExtension -from .constants import ( - data_dir, - codes, - attributes -) +from .constants import data_dir, codes, attributes # register the operators, which isn't done by default when loading bpy # just via headless float_decimals @@ -18,7 +14,9 @@ @pytest.mark.parametrize("code", codes) -def test_op_api_cartoon(snapshot_custom: NumpySnapshotExtension, code, style='ribbon', format="bcif"): +def test_op_api_cartoon( + snapshot_custom: NumpySnapshotExtension, code, style="ribbon", format="bcif" +): scene = bpy.context.scene scene.MN_import_node_setup = True scene.MN_pdb_code = code @@ -39,22 +37,21 @@ def test_op_api_cartoon(snapshot_custom: NumpySnapshotExtension, code, style='ri for name in attributes: if name == "sec_struct" or name.startswith("."): continue - assert snapshot_custom == sample_attribute( - mol, name, evaluate=True) + assert snapshot_custom == sample_attribute(mol, name, evaluate=True) @pytest.mark.parametrize("code", codes) -@pytest.mark.parametrize("file_format", ['bcif', 'cif', 'pdb']) +@pytest.mark.parametrize("file_format", ["bcif", "cif", "pdb"]) def test_op_local(snapshot_custom, code, file_format): scene = bpy.context.scene scene.MN_import_node_setup = False - scene.MN_import_style = 'spheres' + scene.MN_import_style = "spheres" scene.MN_import_build_assembly = False scene.MN_import_del_solvent = False scene.MN_import_format_download = file_format path = str(mn.io.download(code=code, format=file_format, cache=data_dir)) scene.MN_import_local_path = path - scene.MN_centre_type = 'centroid' + scene.MN_centre_type = "centroid" scene.MN_import_centre = False with ObjectTracker() as o: @@ -67,8 +64,7 @@ def test_op_local(snapshot_custom, code, file_format): bob_centred = o.latest() bob_pos, bob_centred_pos = [ - sample_attribute(x, 'position', evaluate=False) - for x in [bob, bob_centred] + sample_attribute(x, "position", evaluate=False) for x in [bob, bob_centred] ] assert snapshot_custom == bob_pos @@ -83,7 +79,7 @@ def test_op_api_mda(snapshot_custom: NumpySnapshotExtension): bpy.context.scene.MN_import_md_topology = topo bpy.context.scene.MN_import_md_trajectory = traj - bpy.context.scene.MN_import_style = 'ribbon' + bpy.context.scene.MN_import_style = "ribbon" bpy.ops.mn.import_protein_md() obj_1 = bpy.context.active_object @@ -91,9 +87,9 @@ def test_op_api_mda(snapshot_custom: NumpySnapshotExtension): assert not bpy.data.collections.get(f"{name}_frames") bpy.context.scene.MN_md_in_memory = True - name = 'NewTrajectoryInMemory' + name = "NewTrajectoryInMemory" - obj_2, universe = mn.io.md.load(topo, traj, name="test", style='ribbon') + obj_2, universe = mn.io.md.load(topo, traj, name="test", style="ribbon") frames_coll = bpy.data.collections.get(f"{obj_2.name}_frames") assert not frames_coll diff --git a/tests/test_parse.py b/tests/test_parse.py index c3d26aba..75557a6d 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -6,7 +6,7 @@ @pytest.fixture def filepath(): - return data_dir / '1f2n.bcif' + return data_dir / "1f2n.bcif" def test_bcif_init(filepath): @@ -36,4 +36,4 @@ def test_bcif_entity_ids(filepath): bcif = mn.io.parse.BCIF(filepath) entity_ids = bcif.entity_ids assert entity_ids is not None - assert entity_ids == ['CAPSID PROTEIN', 'CALCIUM ION', 'water'] + assert entity_ids == ["CAPSID PROTEIN", "CALCIUM ION", "water"] diff --git a/tests/test_pdbx.py b/tests/test_pdbx.py index 60a9e7b3..c817f236 100644 --- a/tests/test_pdbx.py +++ b/tests/test_pdbx.py @@ -7,13 +7,12 @@ def test_ss_label_to_int(): - examples = ['TURN_TY1_P68', 'BEND64', 'HELX_LH_PP_P9', 'STRN44'] - assert [3, 3, 1, 2] == [ - mn.io.parse.cif._ss_label_to_int(x) for x in examples] + examples = ["TURN_TY1_P68", "BEND64", "HELX_LH_PP_P9", "STRN44"] + assert [3, 3, 1, 2] == [mn.io.parse.cif._ss_label_to_int(x) for x in examples] def test_get_ss_from_mmcif(snapshot_custom: NumpySnapshotExtension): - mol = mn.io.load(data_dir / '1cd3.cif') + mol = mn.io.load(data_dir / "1cd3.cif") # mol2, fil2 = mn.io.fetch('1cd3') @@ -26,7 +25,8 @@ def test_get_ss_from_mmcif(snapshot_custom: NumpySnapshotExtension): def test_secondary_structure_no_helix(snapshot_custom): - m = mn.io.fetch('7ZL4', cache_dir=data_dir) + m = mn.io.fetch("7ZL4", cache_dir=data_dir) assert snapshot_custom == sample_attribute( - m.object, 'sec_struct', n=500, evaluate=False) + m.object, "sec_struct", n=500, evaluate=False + ) diff --git a/tests/test_pkg.py b/tests/test_pkg.py index 7d19f995..5d4f205f 100644 --- a/tests/test_pkg.py +++ b/tests/test_pkg.py @@ -9,10 +9,10 @@ def test_name_versions(): def test_is_current(): - assert mn.pkg.is_current('biotite') + assert mn.pkg.is_current("biotite") def test_get_pkgs(): - names = ['biotite', 'MDAnalysis', 'mrcfile', 'starfile', 'msgpack', 'pillow'] + names = ["biotite", "MDAnalysis", "mrcfile", "starfile", "msgpack", "pillow"] for name in mn.pkg.get_pkgs().keys(): assert name in names diff --git a/tests/test_select.py b/tests/test_select.py index 255ba642..29c05975 100644 --- a/tests/test_select.py +++ b/tests/test_select.py @@ -5,11 +5,10 @@ import pytest -def create_debug_group(name='MolecularNodesDebugGroup'): +def create_debug_group(name="MolecularNodesDebugGroup"): group = nodes.new_group(name=name, fallback=False) - info = group.nodes.new('GeometryNodeObjectInfo') - group.links.new(info.outputs['Geometry'], - group.nodes['Group Output'].inputs[0]) + info = group.nodes.new("GeometryNodeObjectInfo") + group.links.new(info.outputs["Geometry"], group.nodes["Group Output"].inputs[0]) return group @@ -20,29 +19,31 @@ def evaluate(object): custom_selections = [ - ('1, 3, 5-7', np.array((1, 3, 5, 6, 7))), - ('5, 9-20', np.append(5, np.arange(9, 21))), - ('1, 7, 8, 9', np.array((1, 7, 8, 9))) + ("1, 3, 5-7", np.array((1, 3, 5, 6, 7))), + ("5, 9-20", np.append(5, np.arange(9, 21))), + ("1, 7, 8, 9", np.array((1, 7, 8, 9))), ] -@pytest.mark.parametrize('selection', custom_selections) +@pytest.mark.parametrize("selection", custom_selections) def test_select_multiple_residues(selection): n_atoms = 100 object = mn.blender.obj.create_object(np.zeros((n_atoms, 3))) - mn.blender.obj.set_attribute(object, 'res_id', np.arange(n_atoms) + 1) + mn.blender.obj.set_attribute(object, "res_id", np.arange(n_atoms) + 1) mod = nodes.get_mod(object) group = nodes.new_group(fallback=False) mod.node_group = group - sep = group.nodes.new('GeometryNodeSeparateGeometry') + sep = group.nodes.new("GeometryNodeSeparateGeometry") nodes.insert_last_node(group, sep) - node_sel_group = nodes.resid_multiple_selection('custom', selection[0]) + node_sel_group = nodes.resid_multiple_selection("custom", selection[0]) node_sel = nodes.add_custom(group, node_sel_group.name) - group.links.new(node_sel.outputs['Selection'], sep.inputs['Selection']) + group.links.new(node_sel.outputs["Selection"], sep.inputs["Selection"]) vertices_count = len(mn.blender.obj.evaluated(object).data.vertices) assert vertices_count == len(selection[1]) - assert (mn.blender.obj.get_attribute( - mn.blender.obj.evaluated(object), 'res_id') == selection[1]).all() + assert ( + mn.blender.obj.get_attribute(mn.blender.obj.evaluated(object), "res_id") + == selection[1] + ).all() diff --git a/tests/test_setup.py b/tests/test_setup.py index a2a1ee18..2ce640be 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -5,4 +5,4 @@ def test_template(): mn.utils.template_install() bpy.ops.wm.read_homefile(app_template="Molecular Nodes") - assert not bpy.data.objects.get('Cube') + assert not bpy.data.objects.get("Cube") diff --git a/tests/test_star.py b/tests/test_star.py index d53df983..8f3458fd 100644 --- a/tests/test_star.py +++ b/tests/test_star.py @@ -9,6 +9,7 @@ try: import pyopenvdb + SKIP = False except ImportError: SKIP = True @@ -21,26 +22,27 @@ def test_starfile_attributes(type): star = starfile.read(file) - if type == 'relion': - df = star['particles'].merge(star['optics'], on='rlnOpticsGroup') - euler_angles = df[['rlnAngleRot', - 'rlnAngleTilt', 'rlnAnglePsi']].to_numpy() + if type == "relion": + df = star["particles"].merge(star["optics"], on="rlnOpticsGroup") + euler_angles = df[["rlnAngleRot", "rlnAngleTilt", "rlnAnglePsi"]].to_numpy() - elif type == 'cistem': + elif type == "cistem": df = star - euler_angles = df[['cisTEMAnglePhi', - 'cisTEMAngleTheta', 'cisTEMAnglePsi']].to_numpy() + euler_angles = df[ + ["cisTEMAnglePhi", "cisTEMAngleTheta", "cisTEMAnglePsi"] + ].to_numpy() # Calculate Scipy rotation from the euler angles rot_from_euler = quats = R.from_euler( - seq='ZYZ', angles=euler_angles, degrees=True + seq="ZYZ", angles=euler_angles, degrees=True ).inv() # Activate the rotation debug mode in the nodetreee and get the quaternion attribute - debugnode = mn.blender.nodes.star_node( - ensemble.node_group).node_tree.nodes['Switch.001'] - debugnode.inputs['Switch'].default_value = True - quat_attribute = ensemble.get_attribute('MNDEBUGEuler', evaluate=True) + debugnode = mn.blender.nodes.star_node(ensemble.node_group).node_tree.nodes[ + "Switch.001" + ] + debugnode.inputs["Switch"].default_value = True + quat_attribute = ensemble.get_attribute("MNDEBUGEuler", evaluate=True) # Convert from blender to scipy conventions and then into Scipy rotation rot_from_geo_nodes = R.from_quat(quat_attribute[:, [1, 2, 3, 0]]) @@ -52,7 +54,7 @@ def test_starfile_attributes(type): def test_categorical_attributes(): file = data_dir / "cistem.star" ensemble = mn.io.star.load(file) - assert 'cisTEMOriginalImageFilename_categories' in ensemble.object + assert "cisTEMOriginalImageFilename_categories" in ensemble.object def test_micrograph_conversion(): @@ -68,32 +70,42 @@ def test_micrograph_conversion(): def test_micrograph_loading(): import bpy + file = data_dir / "cistem.star" tiff_path = data_dir / "montage.tiff" tiff_path.unlink(missing_ok=True) ensemble = mn.io.star.load(file) assert not tiff_path.exists() - ensemble.star_node.inputs['Show Micrograph'].default_value = True + ensemble.star_node.inputs["Show Micrograph"].default_value = True bpy.context.evaluated_depsgraph_get().update() assert tiff_path.exists() # Ensure montage get only loaded once - assert sum(1 for image in bpy.data.images.keys() - if 'montage' in image) == 1 - assert ensemble.micrograph_material.node_tree.nodes['Image Texture'].image.name == 'montage.tiff' - assert ensemble.star_node.inputs['Micrograph'].default_value.name == 'montage.tiff' + assert sum(1 for image in bpy.data.images.keys() if "montage" in image) == 1 + assert ( + ensemble.micrograph_material.node_tree.nodes["Image Texture"].image.name + == "montage.tiff" + ) + assert ensemble.star_node.inputs["Micrograph"].default_value.name == "montage.tiff" -@pytest.mark.skipif(SKIP, reason='Test may segfault on GHA') +@pytest.mark.skipif(SKIP, reason="Test may segfault on GHA") def test_rehydration(tmp_path): import bpy + bpy.ops.wm.read_homefile() ensemble = mn.io.star.load(data_dir / "cistem.star") bpy.ops.wm.save_as_mainfile(filepath=str(tmp_path / "test.blend")) assert ensemble._update_micrograph_texture in bpy.app.handlers.depsgraph_update_post bpy.ops.wm.read_homefile() - assert ensemble._update_micrograph_texture not in bpy.app.handlers.depsgraph_update_post + assert ( + ensemble._update_micrograph_texture + not in bpy.app.handlers.depsgraph_update_post + ) bpy.ops.wm.open_mainfile(filepath=str(tmp_path / "test.blend")) new_ensemble = bpy.types.Scene.MN_starfile_ensembles[0] - assert new_ensemble._update_micrograph_texture in bpy.app.handlers.depsgraph_update_post + assert ( + new_ensemble._update_micrograph_texture + in bpy.app.handlers.depsgraph_update_post + ) assert new_ensemble.data.equals(ensemble.data) diff --git a/tests/utils.py b/tests/utils.py index cc7586b5..d28eeb3c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,53 +11,23 @@ # and when comparing them, reads the list back into a numpy array for comparison # it checks for 'isclose' for floats and otherwise looks for absolute comparison class NumpySnapshotExtension(AmberSnapshotExtension): - def serialize(self, data, **kwargs): if isinstance(data, np.ndarray): return np.array2string( - data, - precision=1, - threshold=1e3, - floatmode='maxprec_equal' + data, precision=1, threshold=1e3, floatmode="maxprec_equal" ) return super().serialize(data, **kwargs) - # def matches(self, *, serialized_data, snapshot_data): - # print(f"HELLOOOO") - # print(f"{serialized_data=}") - # print(f"{snapshot_data=}") - # serialized_data = np.array(ast.literal_eval(serialized_data)), - # snapshot_data = np.array(ast.literal_eval(snapshot_data)), - # print(f"{serialized_data=}") - # print(f"{snapshot_data=}") - - # # super().assert_match(snapshot_custom, test_value) - # # def assert_match(self, snapshot_custom, test_value): - # if isinstance(serialized_data, np.ndarray): - # # if the values are floats, then we use a rough "isclose" to compare them - # # which helps with floating point issues. Between platforms geometry nodes - # # outputs some differences in the meshes which are usually off by ~0.01 or so - - # else: - # assert (serialized_data == np.array(snapshot_data)).all() - - # else: - # super().matches(serialized_data=serialized_data, snapshot_data=snapshot_data) - - -def sample_attribute(object, - attribute, - n=100, - evaluate=True, - error: bool = False, - seed=6): + +def sample_attribute( + object, attribute, n=100, evaluate=True, error: bool = False, seed=6 +): if isinstance(object, mn.io.parse.molecule.Molecule): object = object.object random.seed(seed) if error: - attribute = mn.blender.obj.get_attribute( - object, attribute, evaluate=evaluate) + attribute = mn.blender.obj.get_attribute(object, attribute, evaluate=evaluate) length = len(attribute) if n > length: @@ -72,9 +42,7 @@ def sample_attribute(object, else: try: attribute = mn.blender.obj.get_attribute( - object=object, - name=attribute, - evaluate=evaluate + object=object, name=attribute, evaluate=evaluate ) length = len(attribute) @@ -91,21 +59,17 @@ def sample_attribute(object, return np.array(e) -def sample_attribute_to_string(object, - attribute, - n=100, - evaluate=True, - precision=3, - seed=6): +def sample_attribute_to_string( + object, attribute, n=100, evaluate=True, precision=3, seed=6 +): if isinstance(object, mn.io.parse.molecule.Molecule): object = object.object try: array = sample_attribute( - object, attribute=attribute, n=n, evaluate=evaluate, seed=seed) - except AttributeError as e: - print( - f"Error {e}, unable to sample attribute {attribute} from {object}" + object, attribute=attribute, n=n, evaluate=evaluate, seed=seed ) + except AttributeError as e: + print(f"Error {e}, unable to sample attribute {attribute} from {object}") return str(e) if array.dtype != bool: