diff --git a/.gitignore b/.gitignore index 8980b6ff..f30df0e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.blend1 __pycache__/* .vscode/* -*.zip *.pyc .Rproj.user /.quarto/ diff --git a/MolecularNodes/__init__.py b/MolecularNodes/__init__.py index b2e611b4..b1eb11b2 100644 --- a/MolecularNodes/__init__.py +++ b/MolecularNodes/__init__.py @@ -26,15 +26,20 @@ } from . import auto_load -from .ui import mol_add_node_menu +from .ui import MN_add_node_menu import bpy +import pathlib +import os +from . import pref auto_load.init() def register(): auto_load.register() - bpy.types.NODE_MT_add.append(mol_add_node_menu) + bpy.types.NODE_MT_add.append(MN_add_node_menu) + pref.template_install() def unregister(): - bpy.types.NODE_MT_add.remove(mol_add_node_menu) + bpy.types.NODE_MT_add.remove(MN_add_node_menu) auto_load.unregister() + pref.template_uninstall() diff --git a/MolecularNodes/assembly/mesh.py b/MolecularNodes/assembly/mesh.py index fb55bb4b..0d62b555 100644 --- a/MolecularNodes/assembly/mesh.py +++ b/MolecularNodes/assembly/mesh.py @@ -81,11 +81,11 @@ def transform_chains(assembly, index = 0): def rotation_from_matrix(matrix): from scipy.spatial.transform import Rotation as R + import warnings # calculate the euler rotation from the rotation matrix # Blender is 'xyz' euler rotations. Internally they use matrices / quaternions, but # current interfaces for geometry nodes are just eulers - - rotation = R.from_matrix(matrix).as_euler('xyz') - - return rotation \ No newline at end of file + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + return R.from_matrix(matrix).as_euler('xyz') \ No newline at end of file diff --git a/MolecularNodes/assets/node_append_file.blend b/MolecularNodes/assets/MN_data_file.blend similarity index 63% rename from MolecularNodes/assets/node_append_file.blend rename to MolecularNodes/assets/MN_data_file.blend index a4adfec1..b86b5d5c 100644 Binary files a/MolecularNodes/assets/node_append_file.blend and b/MolecularNodes/assets/MN_data_file.blend differ diff --git a/MolecularNodes/assets/template/Molecular_Nodes.zip b/MolecularNodes/assets/template/Molecular_Nodes.zip new file mode 100644 index 00000000..28f60638 Binary files /dev/null and b/MolecularNodes/assets/template/Molecular_Nodes.zip differ diff --git a/MolecularNodes/assets/template/Molecular_Nodes/__init__.py b/MolecularNodes/assets/template/Molecular_Nodes/__init__.py new file mode 100644 index 00000000..65524dfe --- /dev/null +++ b/MolecularNodes/assets/template/Molecular_Nodes/__init__.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import bpy +# from bpy.app.handlers import persistent + + +# @persistent +# def load_handler(_): + # import bpy + # Apply subdivision modifier on startup + # bpy.ops.object.mode_set(mode='OBJECT') + # if bpy.app.opensubdiv.supported: + # bpy.ops.object.modifier_apply(modifier="Subdivision") + # bpy.ops.object.mode_set(mode='EDIT') + # bpy.ops.transform.tosphere(value=1.0) + # else: + # bpy.ops.object.modifier_remove(modifier="Subdivision") + # bpy.ops.object.mode_set(mode='EDIT') + # bpy.ops.mesh.subdivide(number_cuts=6, smoothness=1.0) + # bpy.ops.object.mode_set(mode='SCULPT') + + +def register(): + # bpy.app.handlers.load_factory_startup_post.append(load_handler) + + +def unregister(): + # bpy.app.handlers.load_factory_startup_post.remove(load_handler) diff --git a/MolecularNodes/assets/template/Molecular_Nodes/startup.blend b/MolecularNodes/assets/template/Molecular_Nodes/startup.blend new file mode 100644 index 00000000..2bbe2cd8 Binary files /dev/null and b/MolecularNodes/assets/template/Molecular_Nodes/startup.blend differ diff --git a/MolecularNodes/color.py b/MolecularNodes/color.py new file mode 100644 index 00000000..abbbdf23 --- /dev/null +++ b/MolecularNodes/color.py @@ -0,0 +1,9 @@ +import random +import colorsys +import numpy as np + +def random_rgb(): + """Random Pastel RGB values + """ + r, g, b = colorsys.hls_to_rgb(random.random(), 0.6, 0.6) + return np.array((r, g, b, 1)) \ No newline at end of file diff --git a/MolecularNodes/density.py b/MolecularNodes/density.py index 20196a27..c48afa94 100644 --- a/MolecularNodes/density.py +++ b/MolecularNodes/density.py @@ -1,20 +1,19 @@ import bpy -# import pyopenvdb as vdb import numpy as np from . import nodes import os -bpy.types.Scene.mol_import_map_nodes = bpy.props.BoolProperty( - name = "mol_import_map_nodes", +bpy.types.Scene.MN_import_map_nodes = bpy.props.BoolProperty( + name = "MN_import_map_nodes", description = "Creating starting node tree for imported map.", default = True ) -bpy.types.Scene.mol_import_map_invert = bpy.props.BoolProperty( - name = "mol_import_map_invert", +bpy.types.Scene.MN_import_map_invert = bpy.props.BoolProperty( + name = "MN_import_map_invert", description = "Invert the values in the map. Low becomes high, high becomes low.", default = False ) -bpy.types.Scene.mol_import_map = bpy.props.StringProperty( +bpy.types.Scene.MN_import_map = bpy.props.StringProperty( name = 'path_map', description = 'File path for the map file.', options = {'TEXTEDIT_UPDATE'}, @@ -166,8 +165,8 @@ def load(file: str, name: str = None, invert: bool = False, world_scale: float = return vol_object -class MOL_OT_Import_Map(bpy.types.Operator): - bl_idname = "mol.import_map" +class MN_OT_Import_Map(bpy.types.Operator): + bl_idname = "mn.import_map" bl_label = "ImportMap" bl_description = "Import a CryoEM map into Blender" bl_options = {"REGISTER"} @@ -177,9 +176,9 @@ def poll(cls, context): return True def execute(self, context): - map_file = bpy.context.scene.mol_import_map - invert = bpy.context.scene.mol_import_map_invert - setup_node_tree = bpy.context.scene.mol_import_map_nodes + map_file = bpy.context.scene.MN_import_map + invert = bpy.context.scene.MN_import_map_invert + setup_node_tree = bpy.context.scene.MN_import_map_nodes vol = load( file = map_file, @@ -194,17 +193,17 @@ def panel(layout_function, scene): col_main = layout_function.column(heading = '', align = False) col_main.label(text = 'Import EM Maps as Volumes') row = col_main.row() - row.prop(bpy.context.scene, 'mol_import_map_nodes', + row.prop(bpy.context.scene, 'MN_import_map_nodes', text = 'Starting Node Tree' ) - row.prop(bpy.context.scene, 'mol_import_map_invert', + row.prop(bpy.context.scene, 'MN_import_map_invert', text = 'Invert Data', emboss = True ) - row.operator('mol.import_map', text = 'Load Map', icon = 'FILE_TICK') + row.operator('mn.import_map', text = 'Load Map', icon = 'FILE_TICK') - col_main.prop(bpy.context.scene, 'mol_import_map', + col_main.prop(bpy.context.scene, 'MN_import_map', text = 'EM Map', emboss = True ) @@ -213,7 +212,7 @@ def panel(layout_function, scene): box.alignment = "LEFT" box.scale_y = 0.4 box.label( - text = f"Intermediate file: {path_to_vdb(bpy.context.scene.mol_import_map)}." + text = f"Intermediate file: {path_to_vdb(bpy.context.scene.MN_import_map)}." ) box.label( text = "Please do not delete this file or the volume will not render." diff --git a/MolecularNodes/esmfold.py b/MolecularNodes/esmfold.py index 07864078..acb51c0f 100644 --- a/MolecularNodes/esmfold.py +++ b/MolecularNodes/esmfold.py @@ -5,7 +5,7 @@ import requests import io -bpy.types.Scene.mol_esmfold_sequence = bpy.props.StringProperty( +bpy.types.Scene.MN_esmfold_sequence = bpy.props.StringProperty( name = 'amino_acid_sequence', description = 'Amino acid sequence of the structure to open', options = {'TEXTEDIT_UPDATE'}, @@ -13,8 +13,8 @@ subtype = 'FILE_PATH', maxlen = 0 ) -bpy.types.Scene.mol_esmfold_name = bpy.props.StringProperty( - name = 'mol_name', +bpy.types.Scene.MN_esmfold_name = bpy.props.StringProperty( + name = 'MN_name', description = 'Name of the molecule on import', options = {'TEXTEDIT_UPDATE'}, default = 'NewMolecule', @@ -24,7 +24,7 @@ def molecule_esmfold( amino_acid_sequence, - mol_name = "Name", + MN_name = "Name", center_molecule = False, del_solvent = True, include_bonds = True, @@ -36,9 +36,9 @@ def molecule_esmfold( include_bonds=include_bonds ) - mol_object, coll_frames = load.create_molecule( - mol_array = mol, - mol_name = mol_name, + MN_object, coll_frames = load.create_molecule( + MN_array = mol, + MN_name = MN_name, file = file, calculate_ss = True, center_molecule = center_molecule, @@ -48,11 +48,11 @@ def molecule_esmfold( if setup_nodes: nodes.create_starting_node_tree( - obj = mol_object, + obj = MN_object, coll_frames=coll_frames, starting_style = starting_style ) - return mol_object + return MN_object def open_structure_esm_fold(amino_acid_sequence, include_bonds=True): import biotite.structure.io.pdb as pdb @@ -89,8 +89,8 @@ def open_structure_esm_fold(amino_acid_sequence, include_bonds=True): raise ValueError(f'ESMFold returned an error for the amino acid sequence input. This is the error message: {r.text}') # operator that calls the function to import the structure from ESMFold -class MOL_OT_Import_Protein_ESMFold(bpy.types.Operator): - bl_idname = "mol.import_protein_esmfold" +class MN_OT_Import_Protein_ESMFold(bpy.types.Operator): + bl_idname = "mn.import_protein_esmfold" bl_label = "import_protein_esmfold" bl_description = "Generate structure from ESMFold" bl_options = {"REGISTER", "UNDO"} @@ -100,21 +100,21 @@ def poll(cls, context): return not False def execute(self, context): - amino_acid_sequence = bpy.context.scene.mol_esmfold_sequence + amino_acid_sequence = bpy.context.scene.MN_esmfold_sequence - mol_object = molecule_esmfold( + MN_object = molecule_esmfold( amino_acid_sequence=amino_acid_sequence, - mol_name=bpy.context.scene.mol_esmfold_name, - include_bonds=bpy.context.scene.mol_import_include_bonds, - center_molecule=bpy.context.scene.mol_import_center, - del_solvent=bpy.context.scene.mol_import_del_solvent, - starting_style=bpy.context.scene.mol_import_default_style, + MN_name=bpy.context.scene.MN_esmfold_name, + include_bonds=bpy.context.scene.MN_import_include_bonds, + center_molecule=bpy.context.scene.MN_import_center, + del_solvent=bpy.context.scene.MN_import_del_solvent, + starting_style=bpy.context.scene.MN_import_default_style, setup_nodes=True ) # return the good news! - bpy.context.view_layer.objects.active = mol_object - self.report({'INFO'}, message=f"Generated protein '{amino_acid_sequence}' as {mol_object.name}") + bpy.context.view_layer.objects.active = MN_object + self.report({'INFO'}, message=f"Generated protein '{amino_acid_sequence}' as {MN_object.name}") return {"FINISHED"} def invoke(self, context, event): @@ -129,13 +129,13 @@ def panel(layout_function, ): col_main.active = True col_main.label(text = "Generate Structure from ESMFold") row_name = col_main.row(align = False) - row_name.prop(bpy.context.scene, 'mol_esmfold_name', + row_name.prop(bpy.context.scene, 'MN_esmfold_name', text = "Name", icon_value = 0, emboss = True) - row_name.operator('mol.import_protein_esmfold', text='Generate', icon='IMPORT') + row_name.operator('mn.import_protein_esmfold', text='Generate', icon='IMPORT') row_seq = col_main.row() row_seq.prop( - bpy.context.scene, 'mol_esmfold_sequence', + bpy.context.scene, 'MN_esmfold_sequence', text = "Sequence", icon_value = 0, emboss = True diff --git a/MolecularNodes/load.py b/MolecularNodes/load.py index 4bc023df..49a20d0e 100644 --- a/MolecularNodes/load.py +++ b/MolecularNodes/load.py @@ -13,7 +13,7 @@ from . import obj import time -bpy.types.Scene.mol_pdb_code = bpy.props.StringProperty( +bpy.types.Scene.MN_pdb_code = bpy.props.StringProperty( name = 'pdb_code', description = 'The 4-character PDB code to download', options = {'TEXTEDIT_UPDATE'}, @@ -21,36 +21,36 @@ subtype = 'NONE', maxlen = 4 ) -bpy.types.Scene.mol_cache_dir = bpy.props.StringProperty( +bpy.types.Scene.MN_cache_dir = bpy.props.StringProperty( name = 'cache_dir', description = 'Location to cache PDB files', options = {'TEXTEDIT_UPDATE'}, default = str(Path('~', '.MolecularNodes').expanduser()), subtype = 'NONE' ) -bpy.types.Scene.mol_import_center = bpy.props.BoolProperty( - name = "mol_import_centre", +bpy.types.Scene.MN_import_center = bpy.props.BoolProperty( + name = "MN_import_centre", description = "Move the imported Molecule on the World Origin", default = False ) -bpy.types.Scene.mol_import_del_solvent = bpy.props.BoolProperty( - name = "mol_import_del_solvent", +bpy.types.Scene.MN_import_del_solvent = bpy.props.BoolProperty( + name = "MN_import_del_solvent", description = "Delete the solvent from the structure on import", default = True ) -bpy.types.Scene.mol_import_include_bonds = bpy.props.BoolProperty( - name = "mol_import_include_bonds", +bpy.types.Scene.MN_import_include_bonds = bpy.props.BoolProperty( + name = "MN_import_include_bonds", description = "Include bonds in the imported structure.", default = True ) -bpy.types.Scene.mol_import_panel_selection = bpy.props.IntProperty( - name = "mol_import_panel_selection", +bpy.types.Scene.MN_import_panel_selection = bpy.props.IntProperty( + name = "MN_import_panel_selection", description = "Import Panel Selection", subtype = 'NONE', default = 0 ) -bpy.types.Scene.mol_import_local_path = bpy.props.StringProperty( +bpy.types.Scene.MN_import_local_path = bpy.props.StringProperty( name = 'path_pdb', description = 'File path of the structure to open', options = {'TEXTEDIT_UPDATE'}, @@ -61,8 +61,8 @@ -bpy.types.Scene.mol_import_local_name = bpy.props.StringProperty( - name = 'mol_name', +bpy.types.Scene.MN_import_local_name = bpy.props.StringProperty( + name = 'MN_name', description = 'Name of the molecule on import', options = {'TEXTEDIT_UPDATE'}, default = 'NewMolecule', @@ -70,8 +70,8 @@ maxlen = 0 ) -bpy.types.Scene.mol_import_default_style = bpy.props.IntProperty( - name = "mol_import_default_style", +bpy.types.Scene.MN_import_default_style = bpy.props.IntProperty( + name = "MN_import_default_style", description = "Default style for importing molecules.", subtype = 'NONE', default = 0 @@ -99,9 +99,9 @@ def molecule_rcsb( start = time.process_time() print('Adding object to scene.') - mol_object, coll_frames = create_molecule( - mol_array = mol, - mol_name = pdb_code, + MN_object, coll_frames = create_molecule( + MN_array = mol, + MN_name = pdb_code, file = file, calculate_ss = False, center_molecule = center_molecule, @@ -112,27 +112,27 @@ def molecule_rcsb( if setup_nodes: nodes.create_starting_node_tree( - obj = mol_object, + obj = MN_object, coll_frames=coll_frames, starting_style = starting_style ) - # mol_object['bio_transform_dict'] = file['bioAssemblyList'] + # MN_object['bio_transform_dict'] = file['bioAssemblyList'] try: parsed_assembly_file = assembly.mmtf.MMTFAssemblyParser(file) - mol_object['biological_assemblies'] = parsed_assembly_file.get_assemblies() + MN_object['biological_assemblies'] = parsed_assembly_file.get_assemblies() except InvalidFileError: pass - return mol_object + return MN_object def molecule_local( file_path, - mol_name = "Name", + MN_name = "Name", include_bonds = True, center_molecule = False, del_solvent = True, @@ -162,7 +162,7 @@ def molecule_local( else: warnings.warn("Unable to open local file. Format not supported.") - # if include_bonds chosen but no bonds currently exist (mol.bonds is None) + # if include_bonds chosen but no bonds currently exist (mn.bonds is None) # then attempt to find bonds by distance if include_bonds and not mol.bonds: mol.bonds = struc.connect_via_distances(mol[0], inter_residue=True) @@ -171,9 +171,9 @@ def molecule_local( file = None - mol_object, coll_frames = create_molecule( - mol_array = mol, - mol_name = mol_name, + MN_object, coll_frames = create_molecule( + MN_array = mol, + MN_name = MN_name, file = file, calculate_ss = True, center_molecule = center_molecule, @@ -184,15 +184,15 @@ def molecule_local( # setup the required initial node tree on the object if setup_nodes: nodes.create_starting_node_tree( - obj = mol_object, + obj = MN_object, coll_frames = coll_frames, starting_style = default_style ) if transforms: - mol_object['biological_assemblies'] = transforms + MN_object['biological_assemblies'] = transforms - return mol_object + return MN_object def get_chain_entity_id(file): entities = file['entityList'] @@ -275,13 +275,13 @@ def pdb_get_b_factors(file): b_factors.append(atoms.b_factor) return b_factors -def get_secondary_structure(mol_array, file) -> np.array: +def get_secondary_structure(MN_array, file) -> np.array: """ Gets the secondary structure annotation that is included in mmtf files and returns it as a numerical numpy array. Parameters: ----------- - mol_array : numpy.array + MN_array : numpy.array The molecular coordinates array, from mmtf.get_structure() file : mmtf.MMTFFile The MMTF file containing the secondary structure information, from mmtf.MMTFFile.read() @@ -326,7 +326,7 @@ def get_secondary_structure(mol_array, file) -> np.array: try: sse = file["secStructList"] except KeyError: - ss_int = np.full(len(mol_array), 3) + ss_int = np.full(len(MN_array), 3) print('Warning: "secStructList" field missing from MMTF file. Defaulting \ to "loop" for all residues.') else: @@ -334,12 +334,12 @@ def get_secondary_structure(mol_array, file) -> np.array: [dssp_to_abc.get(sec_struct_codes.get(ss)) for ss in sse], dtype = int ) - atom_sse = spread_residue_wise(mol_array, ss_int) + atom_sse = spread_residue_wise(MN_array, ss_int) return atom_sse -def comp_secondary_structure(mol_array): +def comp_secondary_structure(MN_array): """Use dihedrals to compute the secondary structure of proteins Through biotite built-in method derivated from P-SEA algorithm (Labesse 1997) @@ -357,14 +357,14 @@ def comp_secondary_structure(mol_array): conv_sse_char_int = {'a': 1, 'b': 2, 'c': 3, '': 0} - char_sse = annotate_sse(mol_array) + char_sse = annotate_sse(MN_array) int_sse = np.array([conv_sse_char_int[char] for char in char_sse], dtype=int) - atom_sse = spread_residue_wise(mol_array, int_sse) + atom_sse = spread_residue_wise(MN_array, int_sse) return atom_sse -def create_molecule(mol_array, - mol_name, +def create_molecule(MN_array, + MN_name, center_molecule = False, file = None, calculate_ss = False, @@ -375,22 +375,22 @@ def create_molecule(mol_array, ): import biotite.structure as struc - mol_frames = None - if isinstance(mol_array, struc.AtomArrayStack): - if mol_array.stack_depth() > 1: - mol_frames = mol_array - mol_array = mol_array[0] + MN_frames = None + if isinstance(MN_array, struc.AtomArrayStack): + if MN_array.stack_depth() > 1: + MN_frames = MN_array + MN_array = MN_array[0] # remove the solvent from the structure if requested if del_solvent: - mol_array = mol_array[np.invert(struc.filter_solvent(mol_array))] + MN_array = MN_array[np.invert(struc.filter_solvent(MN_array))] world_scale = 0.01 - locations = mol_array.coord * world_scale + locations = MN_array.coord * world_scale centroid = np.array([0, 0, 0]) if center_molecule: - centroid = struc.centroid(mol_array) * world_scale + centroid = struc.centroid(MN_array) * world_scale # subtract the centroid from all of the positions to localise the molecule on the world origin @@ -402,13 +402,13 @@ def create_molecule(mol_array, bonds = [] bond_idx = [] - if include_bonds and mol_array.bonds: - bonds = mol_array.bonds.as_array() + if include_bonds and MN_array.bonds: + bonds = MN_array.bonds.as_array() bond_idx = bonds[:, [0, 1]] bond_types = bonds[:, 2].copy(order = 'C') # the .copy(order = 'C') is to fix a weird ordering issue with the resulting array - mol_object = obj.create_object( - name = mol_name, + MN_object = obj.create_object( + name = MN_name, collection = collection, locations = locations, bonds = bond_idx @@ -427,19 +427,19 @@ def create_molecule(mol_array, def att_atomic_number(): atomic_number = np.array(list(map( lambda x: data.elements.get(x, {'atomic_number': -1}).get("atomic_number"), - np.char.title(mol_array.element)))) + np.char.title(MN_array.element)))) return atomic_number def att_res_id(): - return mol_array.res_id + return MN_array.res_id def att_res_name(): other_res = [] counter = 0 id_counter = -1 - res_names = mol_array.res_name + res_names = MN_array.res_name res_names_new = [] - res_ids = mol_array.res_id + res_ids = MN_array.res_id res_nums = [] for name in res_names: @@ -458,32 +458,32 @@ def att_res_name(): res_nums.append(res_num) counter += 1 - mol_object['ligands'] = np.unique(other_res) + MN_object['ligands'] = np.unique(other_res) return np.array(res_nums) def att_chain_id(): - chain_id = np.searchsorted(np.unique(mol_array.chain_id), mol_array.chain_id) + chain_id = np.searchsorted(np.unique(MN_array.chain_id), MN_array.chain_id) return chain_id def att_entity_id(): - return mol_array.entity_id + return MN_array.entity_id def att_b_factor(): - return mol_array.b_factor + return MN_array.b_factor 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, {'vdw_radii': 100}).get('vdw_radii', 100) / 100, - np.char.title(mol_array.element) + np.char.title(MN_array.element) ))) return vdw_radii * world_scale def att_atom_name(): atom_name = np.array(list(map( lambda x: data.atom_names.get(x, 9999), - mol_array.atom_name + MN_array.atom_name ))) return atom_name @@ -491,7 +491,7 @@ def att_atom_name(): def att_lipophobicity(): lipo = np.array(list(map( lambda x, y: data.lipophobicity.get(x, {"0": 0}).get(y, 0), - mol_array.res_name, mol_array.atom_name + MN_array.res_name, MN_array.atom_name ))) return lipo @@ -499,15 +499,15 @@ def att_lipophobicity(): def att_charge(): charge = np.array(list(map( lambda x, y: data.atom_charge.get(x, {"0": 0}).get(y, 0), - mol_array.res_name, mol_array.atom_name + MN_array.res_name, MN_array.atom_name ))) return charge def att_is_alpha(): - return np.isin(mol_array.atom_name, 'CA') + return np.isin(MN_array.atom_name, 'CA') def att_is_solvent(): - return struc.filter_solvent(mol_array) + return struc.filter_solvent(MN_array) def att_is_backbone(): """ @@ -525,31 +525,31 @@ def att_is_backbone(): ] is_backbone = np.logical_and( - np.isin(mol_array.atom_name, backbone_atom_names), - np.logical_not(struc.filter_solvent(mol_array)) + np.isin(MN_array.atom_name, backbone_atom_names), + np.logical_not(struc.filter_solvent(MN_array)) ) return is_backbone def att_is_nucleic(): - return struc.filter_nucleotides(mol_array) + return struc.filter_nucleotides(MN_array) def att_is_peptide(): - aa = struc.filter_amino_acids(mol_array) - con_aa = struc.filter_canonical_amino_acids(mol_array) + aa = struc.filter_amino_acids(MN_array) + con_aa = struc.filter_canonical_amino_acids(MN_array) return aa | con_aa def att_is_hetero(): - return mol_array.hetero + return MN_array.hetero def att_is_carb(): - return struc.filter_carbohydrates(mol_array) + return struc.filter_carbohydrates(MN_array) def att_sec_struct(): if calculate_ss or not file: - return comp_secondary_structure(mol_array) + return comp_secondary_structure(MN_array) else: - return get_secondary_structure(mol_array, file) + return get_secondary_structure(MN_array, file) # Add information about the bond types to the model on the edge domain @@ -559,7 +559,7 @@ def att_sec_struct(): if include_bonds: try: obj.add_attribute( - object = mol_object, + object = MN_object, name = 'bond_type', data = bond_types, type = "INT", @@ -598,23 +598,23 @@ def att_sec_struct(): for att in attributes: start = time.process_time() try: - obj.add_attribute(mol_object, att['name'], att['value'](), att['type'], att['domain']) + obj.add_attribute(MN_object, att['name'], att['value'](), att['type'], att['domain']) print(f'Added {att["name"]} after {time.process_time() - start} s') except: - warnings.warn(f"Unable to add attribute: {att['name']}") + # warnings.warn(f"Unable to add attribute: {att['name']}") print(f'Failed adding {att["name"]} after {time.process_time() - start} s') - if mol_frames: + if MN_frames: try: b_factors = pdb_get_b_factors(file) except: b_factors = None - coll_frames = coll.frames(mol_object.name) + coll_frames = coll.frames(MN_object.name) - for i, frame in enumerate(mol_frames): + for i, frame in enumerate(MN_frames): obj_frame = obj.create_object( - name = mol_object.name + '_frame_' + str(i), + name = MN_object.name + '_frame_' + str(i), collection=coll_frames, locations= frame.coord * world_scale - centroid ) @@ -632,13 +632,13 @@ 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_object['chain_id_unique'] = list(np.unique(mol_array.chain_id)) + MN_object['chain_id_unique'] = list(np.unique(MN_array.chain_id)) except: warnings.warn('No chain information detected.') try: - mol_object['entity_names'] = [ent['description'] for ent in file['entityList']] + MN_object['entity_names'] = [ent['description'] for ent in file['entityList']] except: pass - return mol_object, coll_frames \ No newline at end of file + return MN_object, coll_frames \ No newline at end of file diff --git a/MolecularNodes/md.py b/MolecularNodes/md.py index 75d3d8b5..ee0674cf 100644 --- a/MolecularNodes/md.py +++ b/MolecularNodes/md.py @@ -14,7 +14,7 @@ from . import obj from . import nodes -bpy.types.Scene.mol_import_md_topology = bpy.props.StringProperty( +bpy.types.Scene.MN_import_md_topology = bpy.props.StringProperty( name = 'path_topology', description = 'File path for the toplogy file for the trajectory', options = {'TEXTEDIT_UPDATE'}, @@ -22,7 +22,7 @@ subtype = 'FILE_PATH', maxlen = 0 ) -bpy.types.Scene.mol_import_md_trajectory = bpy.props.StringProperty( +bpy.types.Scene.MN_import_md_trajectory = bpy.props.StringProperty( name = 'path_trajectory', description = 'File path for the trajectory file for the trajectory', options = {'TEXTEDIT_UPDATE'}, @@ -30,33 +30,33 @@ subtype = 'FILE_PATH', maxlen = 0 ) -bpy.types.Scene.mol_import_md_name = bpy.props.StringProperty( - name = 'mol_md_name', +bpy.types.Scene.MN_import_md_name = bpy.props.StringProperty( + name = 'MN_md_name', description = 'Name of the molecule on import', options = {'TEXTEDIT_UPDATE'}, default = 'NewTrajectory', subtype = 'NONE', maxlen = 0 ) -bpy.types.Scene.mol_import_md_frame_start = bpy.props.IntProperty( - name = "mol_import_md_frame_start", +bpy.types.Scene.MN_import_md_frame_start = bpy.props.IntProperty( + name = "MN_import_md_frame_start", description = "Frame start for importing MD trajectory", subtype = 'NONE', default = 0 ) -bpy.types.Scene.mol_import_md_frame_step = bpy.props.IntProperty( - name = "mol_import_md_frame_step", +bpy.types.Scene.MN_import_md_frame_step = bpy.props.IntProperty( + name = "MN_import_md_frame_step", description = "Frame step for importing MD trajectory", subtype = 'NONE', default = 1 ) -bpy.types.Scene.mol_import_md_frame_end = bpy.props.IntProperty( - name = "mol_import_md_frame_end", +bpy.types.Scene.MN_import_md_frame_end = bpy.props.IntProperty( + name = "MN_import_md_frame_end", description = "Frame end for importing MD trajectory", subtype = 'NONE', default = 49 ) -bpy.types.Scene.mol_md_selection = bpy.props.StringProperty( +bpy.types.Scene.MN_md_selection = bpy.props.StringProperty( name = 'md_selection', description = 'Custom selection string when importing MD simulation. See: "https://docs.mdanalysis.org/stable/documentation_pages/selections.html"', options = {'TEXTEDIT_UPDATE'}, @@ -68,8 +68,8 @@ default = 0 ) -class MOL_OT_Import_Protein_MD(bpy.types.Operator): - bl_idname = "mol.import_protein_md" +class MN_OT_Import_Protein_MD(bpy.types.Operator): + bl_idname = "mn.import_protein_md" bl_label = "Import Protein MD" bl_description = "Load molecular dynamics trajectory" bl_options = {"REGISTER", "UNDO"} @@ -79,17 +79,17 @@ def poll(cls, context): return True def execute(self, context): - file_top = bpy.context.scene.mol_import_md_topology - file_traj = bpy.context.scene.mol_import_md_trajectory - name = bpy.context.scene.mol_import_md_name - selection = bpy.context.scene.mol_md_selection - md_start = bpy.context.scene.mol_import_md_frame_start - md_step = bpy.context.scene.mol_import_md_frame_step - md_end = bpy.context.scene.mol_import_md_frame_end - include_bonds = bpy.context.scene.mol_import_include_bonds + file_top = bpy.context.scene.MN_import_md_topology + file_traj = bpy.context.scene.MN_import_md_trajectory + name = bpy.context.scene.MN_import_md_name + selection = bpy.context.scene.MN_md_selection + md_start = bpy.context.scene.MN_import_md_frame_start + md_step = bpy.context.scene.MN_import_md_frame_step + md_end = bpy.context.scene.MN_import_md_frame_end + include_bonds = bpy.context.scene.MN_import_include_bonds custom_selections = bpy.context.scene.trajectory_selection_list - mol_object, coll_frames = load_trajectory( + MN_object, coll_frames = load_trajectory( file_top = file_top, file_traj = file_traj, md_start = md_start, @@ -103,14 +103,14 @@ def execute(self, context): n_frames = len(coll_frames.objects) nodes.create_starting_node_tree( - obj = mol_object, + obj = MN_object, coll_frames = coll_frames, - starting_style = bpy.context.scene.mol_import_default_style + starting_style = bpy.context.scene.MN_import_default_style ) - bpy.context.view_layer.objects.active = mol_object + bpy.context.view_layer.objects.active = MN_object self.report( {'INFO'}, - message=f"Imported '{file_top}' as {mol_object.name} with {str(n_frames)} \ + message=f"Imported '{file_top}' as {MN_object.name} with {str(n_frames)} \ frames from '{file_traj}'." ) @@ -149,7 +149,7 @@ def load_trajectory(file_top, file_traj, name="NewTrajectory", md_start=0, md_en Returns: ------- - mol_object : bpy.types.Object + MN_object : bpy.types.Object The loaded topology file as a blender object. coll_frames : bpy.types.Collection The loaded trajectory as a blender collection. @@ -219,7 +219,7 @@ def load_trajectory(file_top, file_traj, name="NewTrajectory", md_start=0, md_en # create the initial model - mol_object = obj.create_object( + MN_object = obj.create_object( name = name, collection = coll.mn(), locations = univ.atoms.positions * world_scale, @@ -273,7 +273,7 @@ def att_chain_id(): chain_id = univ.atoms.chainIDs chain_id_unique = np.unique(chain_id) chain_id_num = np.array(list(map(lambda x: np.where(x == chain_id_unique)[0][0], chain_id))) - mol_object['chain_id_unique'] = chain_id_unique + MN_object['chain_id_unique'] = chain_id_unique return chain_id_num # returns a numpy array of booleans for each atom, whether or not they are in that selection @@ -317,16 +317,17 @@ def att_is_peptide(): # tries to add the attribute to the mesh by calling the 'value' function which returns # the required values do be added to the domain. try: - obj.add_attribute(mol_object, att['name'], att['value'](), att['type'], att['domain']) + obj.add_attribute(MN_object, att['name'], att['value'](), att['type'], att['domain']) except: - warnings.warn(f"Unable to add attribute: {att['name']}.") + pass + # warnings.warn(f"Unable to add attribute: {att['name']}.") # add the custom selections if they exist if custom_selections: for sel in custom_selections: try: obj.add_attribute( - object=mol_object, + object=MN_object, name=sel.name, data=bool_selection(sel.selection), type = "BOOLEAN", @@ -359,7 +360,7 @@ def att_is_peptide(): # disable the frames collection from the viewer bpy.context.view_layer.layer_collection.children[coll.mn().name].children[coll_frames.name].exclude = True - return mol_object, coll_frames + return MN_object, coll_frames #### UI @@ -387,7 +388,7 @@ class TrajectorySelectionItem(bpy.types.PropertyGroup): type = TrajectorySelectionItem ) -class MOL_UL_TrajectorySelectionListUI(bpy.types.UIList): +class MN_UL_TrajectorySelectionListUI(bpy.types.UIList): """UI List""" def draw_item(self, context, layout, data, item, @@ -437,42 +438,42 @@ def panel(layout_function, scene): col_main.label(text = "Import Molecular Dynamics Trajectories") row_import = col_main.row() row_import.prop( - bpy.context.scene, 'mol_import_md_name', + bpy.context.scene, 'MN_import_md_name', text = "Name", emboss = True ) - row_import.operator('mol.import_protein_md', text = "Load", icon='FILE_TICK') + row_import.operator('mn.import_protein_md', text = "Load", icon='FILE_TICK') row_topology = col_main.row(align = True) row_topology.prop( - bpy.context.scene, 'mol_import_md_topology', + bpy.context.scene, 'MN_import_md_topology', text = 'Topology', emboss = True ) row_trajectory = col_main.row() row_trajectory.prop( - bpy.context.scene, 'mol_import_md_trajectory', + bpy.context.scene, 'MN_import_md_trajectory', text = 'Trajectory', icon_value = 0, emboss = True ) row_frame = col_main.row(heading = "Frames", align = True) row_frame.prop( - bpy.context.scene, 'mol_import_md_frame_start', + bpy.context.scene, 'MN_import_md_frame_start', text = 'Start', emboss = True ) row_frame.prop( - bpy.context.scene, 'mol_import_md_frame_step', + bpy.context.scene, 'MN_import_md_frame_step', text = 'Step', emboss = True ) row_frame.prop( - bpy.context.scene, 'mol_import_md_frame_end', + bpy.context.scene, 'MN_import_md_frame_end', text = 'End', emboss = True ) col_main.prop( - bpy.context.scene, 'mol_md_selection', + bpy.context.scene, 'MN_md_selection', text = 'Import Filter', emboss = True ) @@ -481,7 +482,7 @@ def panel(layout_function, scene): row = col_main.row(align=True) row = row.split(factor = 0.9) - row.template_list('MOL_UL_TrajectorySelectionListUI', 'A list', scene, + 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="") diff --git a/MolecularNodes/nodes.py b/MolecularNodes/nodes.py index 67486d12..1efc2fba 100644 --- a/MolecularNodes/nodes.py +++ b/MolecularNodes/nodes.py @@ -4,6 +4,8 @@ import math from . import obj import numpy as np +from . import color +import warnings socket_types = { 'BOOLEAN' : 'NodeSocketBool', @@ -19,31 +21,36 @@ 'IMAGE' : 'NodeSocketImage' } -def mol_append_node(node_name, link = True): +mn_data_file = os.path.join(pkg.ADDON_DIR, 'assets', 'MN_data_file.blend') + +def append(node_name, link = True): node = bpy.data.node_groups.get(node_name) - if not node or link: - bpy.ops.wm.append( - directory = os.path.join( - pkg.ADDON_DIR, 'assets', 'node_append_file.blend' + r'/NodeTree'), - filename = node_name, - link = link - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + if not node or link: + bpy.ops.wm.append( + directory = os.path.join(mn_data_file, 'NodeTree'), + filename = node_name, + link = link + ) return bpy.data.node_groups[node_name] -def mol_base_material(): - """Append MOL_atomic_material to the .blend file it it doesn't already exist, and return that material.""" +def MN_base_material(): + """ + Append MN_atomic_material to the .blend file it it doesn't already exist, + and return that material. + """ - mat_name = 'MOL_atomic_material' + mat_name = 'MN_atomic_material' mat = bpy.data.materials.get(mat_name) if not mat: + print('appending material') bpy.ops.wm.append( - directory=os.path.join( - pkg.ADDON_DIR, 'assets', 'node_append_file.blend' + r'/Material' - ), - filename='MOL_atomic_material', - link=False + directory = os.path.join(mn_data_file, 'Material'), + filename = 'MN_atomic_material', + link = True ) return bpy.data.materials[mat_name] @@ -68,9 +75,15 @@ def gn_new_group_empty(name = "Geometry Nodes"): group.links.new(output_node.inputs[0], input_node.outputs[0]) return group -def add_custom_node_group(parent_group, node_name, location = [0,0], width = 200, show_options = True): +def add_custom_node_group(parent_group, + node_name, + location = [0,0], + width = 200, + show_options = False, + link = True + ): - mol_append_node(node_name) + append(node_name, link=link) node = parent_group.node_group.nodes.new('GeometryNodeGroup') node.node_tree = bpy.data.node_groups[node_name] @@ -82,9 +95,15 @@ def add_custom_node_group(parent_group, node_name, location = [0,0], width = 200 return node -def add_custom_node_group_to_node(parent_group, node_name, location = [0,0], width = 200, show_options = False): +def add_custom_node_group_to_node(parent_group, + node_name, + location = [0,0], + width = 200, + show_options = False, + link = True + ): - mol_append_node(node_name) + append(node_name, link = link) node = parent_group.nodes.new('GeometryNodeGroup') node.node_tree = bpy.data.node_groups[node_name] @@ -103,7 +122,7 @@ def create_starting_nodes_starfile(obj): node_mod = obj.modifiers.new("MolecularNodes", "NODES") obj.modifiers.active = node_mod - node_name = f"MOL_starfile_{obj.name}" + node_name = f"MN_starfile_{obj.name}" # if node tree already exists by this name, set it and return it node_group = bpy.data.node_groups.get(node_name) @@ -213,7 +232,7 @@ def create_starting_nodes_density(obj, threshold = 0.8): if not node_mod: node_mod = obj.modifiers.new("MolecularNodes", "NODES") obj.modifiers.active = node_mod - node_name = f"MOL_density_{obj.name}" + node_name = f"MN_density_{obj.name}" # if node tree already exists by this name, set it and return it node_group = bpy.data.node_groups.get(node_name) @@ -231,8 +250,8 @@ def create_starting_nodes_density(obj, threshold = 0.8): node_output = node_mod.node_group.nodes[bpy.app.translations.pgettext_data("Group Output",)] node_output.location = [800, 0] - node_density = add_custom_node_group(node_mod, 'MOL_style_density_surface', [400, 0]) - node_density.inputs['Material'].default_value = mol_base_material() + node_density = add_custom_node_group(node_mod, 'MN_style_density_surface', [400, 0]) + node_density.inputs['Material'].default_value = MN_base_material() node_density.inputs['Density Threshold'].default_value = threshold @@ -257,7 +276,7 @@ def create_starting_node_tree(obj, coll_frames, starting_style = "atoms"): obj.modifiers.active = node_mod - name = f"MOL_{obj.name}" + name = f"MN_{obj.name}" # if node group of this name already exists, set that node group # and return it without making any changes node_group = bpy.data.node_groups.get(name) @@ -273,44 +292,38 @@ def create_starting_node_tree(obj, coll_frames, starting_style = "atoms"): node_input = node_mod.node_group.nodes[bpy.app.translations.pgettext_data("Group Input",)] node_input.location = [0, 0] node_output = node_mod.node_group.nodes[bpy.app.translations.pgettext_data("Group Output",)] - node_output.location = [800, 0] - - # node_properties = add_custom_node_group(node_group, 'MOL_prop_setup', [0, 0]) - node_colour = add_custom_node_group(node_mod, 'MOL_color_set_common', [200, 0]) + node_output.location = [700, 0] + # node_properties = add_custom_node_group(node_group, 'MN_prop_setup', [0, 0]) + node_color_set = add_custom_node_group(node_mod, 'MN_color_set', [200, 0]) + node_color_common = add_custom_node_group(node_mod, 'MN_color_common', [-50, -150]) - node_random_color = add_custom_node_group(node_mod, 'MOL_color_random', [-60, -200]) - # node_random_colour = node_group.nodes.new("FunctionNodeRandomValue") - # node_random_colour.data_type = 'FLOAT_VECTOR' - # node_random_colour.location = [-60, -200] - # node_chain_id = node_group.nodes.new("GeometryNodeInputNamedAttribute") - # node_chain_id.location = [-250, -450] - # node_chain_id.data_type = "INT" - # node_chain_id.inputs['Name'].default_value = "chain_id" + node_random_color = add_custom_node_group(node_mod, 'MN_color_attribute_random', [-300, -150]) # create the links between the the nodes that have been established link = node_group.links.new - link(node_input.outputs['Geometry'], node_colour.inputs[0]) - link(node_colour.outputs[0], node_output.inputs['Geometry']) - link(node_random_color.outputs['Color'], node_colour.inputs['Carbon']) + link(node_input.outputs['Geometry'], node_color_set.inputs[0]) + link(node_color_set.outputs[0], node_output.inputs['Geometry']) + link(node_random_color.outputs['Color'], node_color_common.inputs['Carbon']) + link(node_color_common.outputs[0], node_color_set.inputs['Color']) # link(node_chain_id.outputs[4], node_random_colour.inputs['ID']) styles = [ - 'MOL_style_atoms_cycles', - 'MOL_style_cartoon', - 'MOL_style_ribbon_protein', - 'MOL_style_ball_and_stick' + 'MN_style_atoms', + 'MN_style_cartoon', + 'MN_style_ribbon_protein', + 'MN_style_ball_and_stick' ] # if starting_style == "atoms": - node_style = add_custom_node_group(node_mod, styles[starting_style], location = [500, 0]) - link(node_colour.outputs['Atoms'], node_style.inputs['Atoms']) + node_style = add_custom_node_group(node_mod, styles[starting_style], location = [450, 0]) + link(node_color_set.outputs['Atoms'], node_style.inputs['Atoms']) link(node_style.outputs[0], node_output.inputs['Geometry']) - node_style.inputs['Material'].default_value = mol_base_material() + node_style.inputs['Material'].default_value = MN_base_material() # if multiple frames, set up the required nodes for an animation @@ -318,13 +331,13 @@ def create_starting_node_tree(obj, coll_frames, starting_style = "atoms"): node_output.location = [1100, 0] node_style.location = [800, 0] - node_animate_frames = add_custom_node_group_to_node(node_group, 'MOL_animate_frames', [500, 0]) + node_animate_frames = add_custom_node_group_to_node(node_group, 'MN_animate_frames', [500, 0]) node_animate_frames.inputs['Frames'].default_value = coll_frames # node_animate_frames.inputs['Absolute Frame Position'].default_value = True - node_animate = add_custom_node_group_to_node(node_group, 'MOL_animate_value', [500, -300]) - link(node_colour.outputs['Atoms'], node_animate_frames.inputs['Atoms']) + node_animate = add_custom_node_group_to_node(node_group, 'MN_animate_value', [500, -300]) + link(node_color_set.outputs['Atoms'], node_animate_frames.inputs['Atoms']) link(node_animate_frames.outputs['Atoms'], node_style.inputs['Atoms']) link(node_animate.outputs['Animate 0..1'], node_animate_frames.inputs['Animate 0..1']) @@ -360,7 +373,7 @@ def split_geometry_to_instances(name, iter_list=('A', 'B', 'C'), attribute='chai pos = [i % 10, math.floor(i / 10)] - node_split = add_custom_node_group_to_node(node_group, 'MOL_utils_split_instance') + node_split = add_custom_node_group_to_node(node_group, 'MN_utils_split_instance') node_split.location = [int(250 * pos[0]), int(-300 * pos[1])] node_split.inputs['Group ID'].default_value = i @@ -393,7 +406,7 @@ def nodes_to_geometry(this_group, node_list, output = 'Geometry', join_offset = def create_assembly_node_tree(name, iter_list, data_object): - node_group_name = f"MOL_assembly_{name}" + node_group_name = f"MN_assembly_{name}" group = bpy.data.node_groups.get(node_group_name) if group: return group @@ -403,12 +416,12 @@ def create_assembly_node_tree(name, iter_list, data_object): n_assemblies = len(np.unique(obj.get_attribute(data_object, 'assembly_id'))) node_group_instances = split_geometry_to_instances( - name = f"MOL_utils_split_{name}", + name = f"MN_utils_split_{name}", iter_list = iter_list, attribute = 'chain_id' ) - node_group_assembly_instance = mol_append_node('MOL_assembly_instance_chains') + node_group_assembly_instance = append('MN_assembly_instance_chains') def new_node_group(name, location = [0, 0]): node = group.nodes.new("GeometryNodeGroup") @@ -467,7 +480,7 @@ def create_custom_surface(name, n_chains): return group # get the node to create a loop from - looping_node = mol_append_node('MOL_style_surface_single') + looping_node = append('MN_style_surface_single') # create new empty data block @@ -564,7 +577,7 @@ def rotation_matrix(node_group, mat, location = [0,0], world_scale = 0.01): """ from scipy.spatial.transform import Rotation as R - node_utils_rot = mol_append_node('MOL_utils_rot_trans') + node_utils_rot = append('MN_utils_rot_trans') node = node_group.nodes.new('GeometryNodeGroup') node.node_tree = node_utils_rot @@ -632,7 +645,7 @@ def chain_selection(node_name, input_list, attribute = 'chain_id', starting_valu counter = 0 for chain_name in input_list: current_node = chain_group.nodes.new("GeometryNodeGroup") - current_node.node_tree = mol_append_node('.utils_bool_chain') + current_node.node_tree = append('.MN_utils_bool_chain') current_node.location = [counter * node_sep_dis, 200] current_node.inputs["number_matched"].default_value = counter + starting_value group_link = chain_group.links.new @@ -737,7 +750,7 @@ def chain_color(node_name, input_list, label_prefix = "Chain ", field = "chain_i # create an input for this chain chain_group.inputs.new("NodeSocketColor", current_chain) - chain_group.inputs[current_chain].default_value = [random.random(), random.random(), random.random(), 1] + chain_group.inputs[current_chain].default_value = color.random_rgb() # switch input colours 10 and 11 link(node_input.outputs[current_chain], node_color.inputs[11]) link(node_compare.outputs['Result'], node_color.inputs['Switch']) @@ -811,11 +824,11 @@ def resid_multiple_selection(node_name, input_resid_string): # set a new input and set the resid residue_id_group.inputs.new("NodeSocketInt",'res_id').default_value = int(residue_id) - # set a counter for MOL_sel_res_id* nodes + # set a counter for Select Res ID* nodes counter=0 for residue_id_index,residue_id in enumerate(sub_list): - # add an new node of MOL_sel_res_id or MOL_sek_res_id_range + # add an new node of Select Res ID or MN_sek_res_id_range current_node = new_node("GeometryNodeGroup") # add an bool_math block @@ -825,7 +838,7 @@ def resid_multiple_selection(node_name, input_resid_string): if '-' in residue_id: # a residue range - current_node.node_tree = mol_append_node('MOL_sel_res_id_range') + current_node.node_tree = append('MN_select_res_ID_range') group_link(residue_id_group_in.outputs[counter], current_node.inputs[0]) counter+=1 @@ -834,8 +847,8 @@ def resid_multiple_selection(node_name, input_resid_string): else: # create a node - current_node.node_tree = mol_append_node('MOL_sel_res_id') - # link the input of MOL_sel_res_id + current_node.node_tree = append('MN_select_res_ID') + # link the input of MN_select_res_ID* #print(f'counter={counter} of {residue_id}') group_link(residue_id_group_in.outputs[counter], current_node.inputs[0]) diff --git a/MolecularNodes/obj.py b/MolecularNodes/obj.py index 005551aa..dd5ef58c 100644 --- a/MolecularNodes/obj.py +++ b/MolecularNodes/obj.py @@ -42,11 +42,11 @@ def create_object(name: str, collection: bpy.types.Collection, locations, bonds= ``` """ # create a new mesh - mol_mesh = bpy.data.meshes.new(name) - mol_mesh.from_pydata(locations, bonds, faces=[]) - mol_object = bpy.data.objects.new(name, mol_mesh) - collection.objects.link(mol_object) - return mol_object + MN_mesh = bpy.data.meshes.new(name) + MN_mesh.from_pydata(locations, bonds, faces=[]) + MN_object = bpy.data.objects.new(name, MN_mesh) + collection.objects.link(MN_object) + return MN_object def add_attribute(object: bpy.types.Object, name: str, data, type="FLOAT", domain="POINT"): diff --git a/MolecularNodes/pkg.py b/MolecularNodes/pkg.py index 0ce3a80f..04e00b65 100644 --- a/MolecularNodes/pkg.py +++ b/MolecularNodes/pkg.py @@ -389,8 +389,8 @@ def install_all_packages(pypi_mirror_provider: str='Default') -> list: raise InstallationError(f"Error installing package {pkg.get('name')}: {str(e)}") return results -class MOL_OT_Install_Package(bpy.types.Operator): - bl_idname = 'mol.install_package' +class MN_OT_Install_Package(bpy.types.Operator): + bl_idname = 'mn.install_package' bl_label = 'Install Given Python Package' bl_options = {'REGISTER', 'INTERNAL'} package: bpy.props.StringProperty( diff --git a/MolecularNodes/pref.py b/MolecularNodes/pref.py index bf5f094b..e2705e4d 100644 --- a/MolecularNodes/pref.py +++ b/MolecularNodes/pref.py @@ -1,6 +1,14 @@ import bpy +import os +import traceback +import zipfile +import pathlib from . import pkg from bpy.types import AddonPreferences +from bpy.app.translations import pgettext_tip as tip_ + +install_instructions = "https://bradyajohnston.github.io/MolecularNodes/installation.html#installing-biotite-mdanalysis" +ADDON_DIR = pathlib.Path(__file__).resolve().parent bpy.types.Scene.pypi_mirror_provider = bpy.props.StringProperty( name = 'pypi_mirror_provider', @@ -16,7 +24,7 @@ def button_install_pkg(layout, name, version, desc = ''): if pkg.is_available(name, version): row = layout.row() row.label(text=f"{name} version {version} is installed.") - op = row.operator('mol.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}' @@ -25,7 +33,7 @@ def button_install_pkg(layout, name, version, desc = ''): col = row.column() col.label(text=str(desc)) col = row.column() - op = col.operator('mol.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}' @@ -61,4 +69,110 @@ def draw(self, context): box.label(text = "On M1/M2 macOS machines, extra install steps are required.") box.operator( "wm.url_open", text = "Installation Instructions", icon = 'HELP' - ).url = "https://bradyajohnston.github.io/MolecularNodes/installation.html#installing-biotite-mdanalysis" \ No newline at end of file + ).url = install_instructions + +def _module_filesystem_remove(path_base, module_name): + # taken from the bpy.ops.preferences.app_template_install() operator source code + # Remove all Python modules with `module_name` in `base_path`. + # 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] + if f_base == module_name: + f_full = os.path.join(path_base, f) + if os.path.isdir(f_full): + shutil.rmtree(f_full) + else: + os.remove(f_full) + +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. + # use `os.path.normpath` instead of `f.removesuffix(os.sep)` + # since paths could be stored as `./paths/./`. + # + # Note that `..` prefixed paths can exist in ZIP files but they don't write to parent directory when extracting. + # Nor do they pass the `os.sep not in f` test, this is important, + # otherwise `shutil.rmtree` below could made to remove directories outside the installation directory. + f = os.path.normpath(f) + if os.sep not in f: + root_paths.append(f) + return root_paths + +def template_install(): + 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), 'Molecular_Nodes') + if os.path.exists(path): + shutil.rmtree(path) + bpy.utils.refresh_script_paths() + +def _install_template(filepath, overwrite = True): + # taken from the bpy.ops.preferences.app_template_install() operator source code + + path_app_templates = bpy.utils.user_resource( + 'SCRIPTS', + path=os.path.join("startup", "bl_app_templates_user"), + create=True, + ) + + if not os.path.isdir(path_app_templates): + try: + os.makedirs(path_app_templates, exist_ok=True) + except: + traceback.print_exc() + + app_templates_old = set(os.listdir(path_app_templates)) + + # check to see if the file is in compressed format (.zip) + if zipfile.is_zipfile(filepath): + try: + file_to_extract = zipfile.ZipFile(filepath, 'r') + except: + traceback.print_exc() + return {'CANCELLED'} + + file_to_extract_root = _zipfile_root_namelist(file_to_extract) + if overwrite: + for f in file_to_extract_root: + _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)) + if os.path.exists(path_dest): + # self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest) + return {'CANCELLED'} + + try: # extract the file to "bl_app_templates_user" + file_to_extract.extractall(path_app_templates) + except: + traceback.print_exc() + return {'CANCELLED'} + + else: + # Only support installing zipfiles + print('no zipfile') + return {'CANCELLED'} + + app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old + + # in case a new module path was created to install this addon. + 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) + ) + print(msg) \ No newline at end of file diff --git a/MolecularNodes/star.py b/MolecularNodes/star.py index 118bdffd..7d4ca340 100644 --- a/MolecularNodes/star.py +++ b/MolecularNodes/star.py @@ -7,7 +7,7 @@ -bpy.types.Scene.mol_import_star_file_path = bpy.props.StringProperty( +bpy.types.Scene.MN_import_star_file_path = bpy.props.StringProperty( name = 'star_file_path', description = 'File path for the star file to import.', options = {'TEXTEDIT_UPDATE'}, @@ -15,7 +15,7 @@ subtype = 'FILE_PATH', maxlen = 0 ) -bpy.types.Scene.mol_import_star_file_name = bpy.props.StringProperty( +bpy.types.Scene.MN_import_star_file_name = bpy.props.StringProperty( name = 'star_file_name', description = 'Name of the created object.', options = {'TEXTEDIT_UPDATE'}, @@ -121,21 +121,21 @@ def panel(layout_function, scene): col_main.label(text = "Import Star File") row_import = col_main.row() row_import.prop( - bpy.context.scene, 'mol_import_star_file_name', + bpy.context.scene, 'MN_import_star_file_name', text = 'Name', emboss = True ) col_main.prop( - bpy.context.scene, 'mol_import_star_file_path', + bpy.context.scene, 'MN_import_star_file_path', text = '.star File Path', emboss = True ) - row_import.operator('mol.import_star_file', text = 'Load', icon = 'FILE_TICK') + row_import.operator('mn.import_star_file', text = 'Load', icon = 'FILE_TICK') -class MOL_OT_Import_Star_File(bpy.types.Operator): - bl_idname = "mol.import_star_file" +class MN_OT_Import_Star_File(bpy.types.Operator): + bl_idname = "mn.import_star_file" bl_label = "Import Star File" bl_description = "Will import the given file, setting up the points to instance an object." bl_options = {"REGISTER"} @@ -146,8 +146,8 @@ def poll(cls, context): def execute(self, context): load_star_file( - file_path = bpy.context.scene.mol_import_star_file_path, - obj_name = bpy.context.scene.mol_import_star_file_name, + file_path = bpy.context.scene.MN_import_star_file_path, + obj_name = bpy.context.scene.MN_import_star_file_name, node_tree = True ) return {"FINISHED"} \ No newline at end of file diff --git a/MolecularNodes/ui.py b/MolecularNodes/ui.py index e3e7d3fd..9c68e65a 100644 --- a/MolecularNodes/ui.py +++ b/MolecularNodes/ui.py @@ -3,16 +3,13 @@ from . import pkg from . import load from . import md -from . import assembly from . import density from . import star from . import esmfold -from . import density -import os # operator that calls the function to import the structure from the PDB -class MOL_OT_Import_Protein_RCSB(bpy.types.Operator): - bl_idname = "mol.import_protein_rcsb" +class MN_OT_Import_Protein_RCSB(bpy.types.Operator): + bl_idname = "mn.import_protein_rcsb" bl_label = "import_protein_fetch_pdb" bl_description = "Download and open a structure from the Protein Data Bank" bl_options = {"REGISTER", "UNDO"} @@ -22,19 +19,19 @@ def poll(cls, context): return not False def execute(self, context): - pdb_code = bpy.context.scene.mol_pdb_code + pdb_code = bpy.context.scene.MN_pdb_code - mol_object = load.molecule_rcsb( + MN_object = load.molecule_rcsb( pdb_code=pdb_code, - center_molecule=bpy.context.scene.mol_import_center, - del_solvent=bpy.context.scene.mol_import_del_solvent, - include_bonds=bpy.context.scene.mol_import_include_bonds, - starting_style=bpy.context.scene.mol_import_default_style, - cache_dir=bpy.context.scene.mol_cache_dir + center_molecule=bpy.context.scene.MN_import_center, + del_solvent=bpy.context.scene.MN_import_del_solvent, + include_bonds=bpy.context.scene.MN_import_include_bonds, + starting_style=bpy.context.scene.MN_import_default_style, + cache_dir=bpy.context.scene.MN_cache_dir ) - bpy.context.view_layer.objects.active = mol_object - self.report({'INFO'}, message=f"Imported '{pdb_code}' as {mol_object.name}") + bpy.context.view_layer.objects.active = MN_object + self.report({'INFO'}, message=f"Imported '{pdb_code}' as {MN_object.name}") return {"FINISHED"} @@ -43,8 +40,8 @@ def invoke(self, context, event): # operator that calls the function to import the structure from a local file -class MOL_OT_Import_Protein_Local(bpy.types.Operator): - bl_idname = "mol.import_protein_local" +class MN_OT_Import_Protein_Local(bpy.types.Operator): + bl_idname = "mn.import_protein_local" bl_label = "import_protein_local" bl_description = "Open a local structure file" bl_options = {"REGISTER", "UNDO"} @@ -54,21 +51,21 @@ def poll(cls, context): return not False def execute(self, context): - file_path = bpy.context.scene.mol_import_local_path + file_path = bpy.context.scene.MN_import_local_path - mol_object = load.molecule_local( + MN_object = load.molecule_local( file_path=file_path, - mol_name=bpy.context.scene.mol_import_local_name, - include_bonds=bpy.context.scene.mol_import_include_bonds, - center_molecule=bpy.context.scene.mol_import_center, - del_solvent=bpy.context.scene.mol_import_del_solvent, - default_style=bpy.context.scene.mol_import_default_style, + MN_name=bpy.context.scene.MN_import_local_name, + include_bonds=bpy.context.scene.MN_import_include_bonds, + center_molecule=bpy.context.scene.MN_import_center, + del_solvent=bpy.context.scene.MN_import_del_solvent, + default_style=bpy.context.scene.MN_import_default_style, setup_nodes=True ) # return the good news! - bpy.context.view_layer.objects.active = mol_object - self.report({'INFO'}, message=f"Imported '{file_path}' as {mol_object.name}") + bpy.context.view_layer.objects.active = MN_object + self.report({'INFO'}, message=f"Imported '{file_path}' as {MN_object.name}") return {"FINISHED"} def invoke(self, context, event): @@ -76,7 +73,7 @@ def invoke(self, context, event): -def MOL_PT_panel_rcsb(layout_function, ): +def MN_PT_panel_rcsb(layout_function, ): col_main = layout_function.column(heading = '', align = False) col_main.alert = False col_main.enabled = True @@ -89,27 +86,27 @@ def MOL_PT_panel_rcsb(layout_function, ): col_main.label(text = "Download from PDB") col_main.prop( bpy.context.scene, - 'mol_cache_dir', + 'MN_cache_dir', text = 'Cache dir') row_import = col_main.row() - row_import.prop(bpy.context.scene, 'mol_pdb_code', text='PDB ID') - row_import.operator('mol.import_protein_rcsb', text='Download', icon='IMPORT') + row_import.prop(bpy.context.scene, 'MN_pdb_code', text='PDB ID') + row_import.operator('mn.import_protein_rcsb', text='Download', icon='IMPORT') -def MOL_PT_panel_local(layout_function, ): +def MN_PT_panel_local(layout_function, ): col_main = layout_function.column(heading = '', align = False) col_main.alert = False col_main.enabled = True col_main.active = True col_main.label(text = "Open Local File") row_name = col_main.row(align = False) - row_name.prop(bpy.context.scene, 'mol_import_local_name', + row_name.prop(bpy.context.scene, 'MN_import_local_name', text = "Name", icon_value = 0, emboss = True) - row_name.operator('mol.import_protein_local', text = "Load", + row_name.operator('mn.import_protein_local', text = "Load", icon='FILE_TICK', emboss = True) row_import = col_main.row() row_import.prop( - bpy.context.scene, 'mol_import_local_path', + bpy.context.scene, 'MN_import_local_path', text = "File path", icon_value = 0, emboss = True @@ -117,12 +114,12 @@ def MOL_PT_panel_local(layout_function, ): -class MOL_OT_Import_Method_Selection(bpy.types.Operator): - bl_idname = "mol.import_method_selection" +class MN_OT_Import_Method_Selection(bpy.types.Operator): + bl_idname = "mn.import_method_selection" bl_label = "import_method" bl_description = "Change Structure Import Method" bl_options = {"REGISTER", "UNDO"} - mol_interface_value: bpy.props.IntProperty( + MN_interface_value: bpy.props.IntProperty( name = 'interface_value', description = '', default = 0, @@ -134,33 +131,33 @@ def poll(cls, context): return not False def execute(self, context): - bpy.context.scene.mol_import_panel_selection = self.mol_interface_value + bpy.context.scene.MN_import_panel_selection = self.MN_interface_value return {"FINISHED"} def invoke(self, context, event): return self.execute(context) -def MOL_change_import_interface(layout_function, label, interface_value, icon): +def MN_change_import_interface(layout_function, label, interface_value, icon): if isinstance(icon, str): op = layout_function.operator( - 'mol.import_method_selection', + 'mn.import_method_selection', text = label, icon = icon, emboss = True, - depress = interface_value == bpy.context.scene.mol_import_panel_selection + depress = interface_value == bpy.context.scene.MN_import_panel_selection ) elif isinstance(icon, int): op = layout_function.operator( - 'mol.import_method_selection', + 'mn.import_method_selection', text = label, icon_value = icon, emboss = True, - depress = interface_value == bpy.context.scene.mol_import_panel_selection + depress = interface_value == bpy.context.scene.MN_import_panel_selection ) - op.mol_interface_value = interface_value + op.MN_interface_value = interface_value -class MOL_OT_Default_Style(bpy.types.Operator): - bl_idname = "mol.default_style" +class MN_OT_Default_Style(bpy.types.Operator): + bl_idname = "mn.default_style" bl_label = "Change the default style." bl_description = "Change the default style of molecules on import." bl_options = {"REGISTER", "UNDO"} @@ -171,21 +168,21 @@ def poll(cls, context): return True def execute(self, context): - bpy.context.scene.mol_import_default_style = self.panel_display + bpy.context.scene.MN_import_default_style = self.panel_display return {"FINISHED"} def default_style(layout, label, panel_display): op = layout.operator( - 'mol.default_style', + 'mn.default_style', text = label, emboss = True, - depress = (panel_display == bpy.context.scene.mol_import_default_style) + depress = (panel_display == bpy.context.scene.MN_import_default_style) ) op.panel_display = panel_display -class MOL_MT_Default_Style(bpy.types.Menu): +class MN_MT_Default_Style(bpy.types.Menu): bl_label = "" - bl_idname = "MOL_MT_Default_Style" + bl_idname = "MN_MT_Default_Style" @classmethod def poll(cls, context): @@ -198,21 +195,21 @@ def draw(self, context): default_style(layout, 'Ribbon', 2) default_style(layout, 'Ball and Stick', 3) -def MOL_PT_panel_ui(layout_function, scene): +def MN_PT_panel_ui(layout_function, scene): layout_function.label(text = "Import Options", icon = "MODIFIER") box = layout_function.box() grid = box.grid_flow(columns = 2) - grid.prop(bpy.context.scene, 'mol_import_center', + grid.prop(bpy.context.scene, 'MN_import_center', text = 'Centre Structure', icon_value=0, emboss=True) - grid.prop(bpy.context.scene, 'mol_import_del_solvent', + grid.prop(bpy.context.scene, 'MN_import_del_solvent', text = 'Delete Solvent', icon_value=0, emboss=True) - grid.prop(bpy.context.scene, 'mol_import_include_bonds', + grid.prop(bpy.context.scene, 'MN_import_include_bonds', text = 'Import Bonds', icon_value=0, emboss=True) grid.menu( - 'MOL_MT_Default_Style', + 'MN_MT_Default_Style', text = ['Atoms', 'Cartoon', 'Ribbon', 'Ball and Stick'][ - bpy.context.scene.mol_import_default_style + bpy.context.scene.MN_import_default_style ]) panel = layout_function # row = panel.row(heading = '', align=True) @@ -222,14 +219,14 @@ def MOL_PT_panel_ui(layout_function, scene): row.alert = False - MOL_change_import_interface(row, 'PDB', 0, "URL") - MOL_change_import_interface(row, 'ESMFold', 1, "URL") - MOL_change_import_interface(row, 'Local File', 2, 108) - MOL_change_import_interface(row, 'MD Trajectory', 3, 487) - MOL_change_import_interface(row, 'EM Map', 4, 'LIGHTPROBE_CUBEMAP') - MOL_change_import_interface(row, 'Star File', 5, 487) + MN_change_import_interface(row, 'PDB', 0, "URL") + MN_change_import_interface(row, 'ESMFold', 1, "URL") + MN_change_import_interface(row, 'Local File', 2, 108) + MN_change_import_interface(row, 'MD Trajectory', 3, 487) + MN_change_import_interface(row, 'EM Map', 4, 'LIGHTPROBE_CUBEMAP') + MN_change_import_interface(row, 'Star File', 5, 487) - panel_selection = bpy.context.scene.mol_import_panel_selection + panel_selection = bpy.context.scene.MN_import_panel_selection col = panel.column() box = col.box() @@ -240,7 +237,7 @@ def MOL_PT_panel_ui(layout_function, scene): box.alert = True box.label(text = "Please install biotite in the addon preferences.") - MOL_PT_panel_rcsb(box) + MN_PT_panel_rcsb(box) elif panel_selection == 1: if not pkg.is_current('biotite'): box.enabled = False @@ -252,7 +249,7 @@ def MOL_PT_panel_ui(layout_function, scene): box.enabled = False box.alert = True box.label(text = "Please install biotite in the addon preferences.") - MOL_PT_panel_local(box) + MN_PT_panel_local(box) elif panel_selection == 3: if not pkg.is_current('MDAnalysis'): box.enabled = False @@ -275,9 +272,9 @@ def MOL_PT_panel_ui(layout_function, scene): star.panel(box, scene) -class MOL_PT_panel(bpy.types.Panel): +class MN_PT_panel(bpy.types.Panel): bl_label = 'Molecular Nodes' - bl_idname = 'MOL_PT_panel' + bl_idname = 'MN_PT_panel' bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = 'scene' @@ -294,9 +291,9 @@ def draw_header(self, context): def draw(self, context): - MOL_PT_panel_ui(self.layout, bpy.context.scene) + MN_PT_panel_ui(self.layout, bpy.context.scene) -def mol_add_node(node_name, label: str = '', show_options = True): +def MN_add_node(node_name, label: str = '', show_options = False): prev_context = bpy.context.area.type bpy.context.area.type = 'NODE_EDITOR' # actually invoke the operator to add a node to the current node tree @@ -318,10 +315,10 @@ def mol_add_node(node_name, label: str = '', show_options = True): # if added node has a 'Material' input, set it to the default MN material input_mat = bpy.context.active_node.inputs.get('Material') if input_mat: - input_mat.default_value = nodes.mol_base_material() + input_mat.default_value = nodes.MN_base_material() -class MOL_OT_Add_Custom_Node_Group(bpy.types.Operator): - bl_idname = "mol.add_custom_node_group" +class MN_OT_Add_Custom_Node_Group(bpy.types.Operator): + bl_idname = "mn.add_custom_node_group" bl_label = "Add Custom Node Group" # bl_description = "Add Molecular Nodes custom node group." bl_options = {"REGISTER", "UNDO"} @@ -339,6 +336,7 @@ class MOL_OT_Add_Custom_Node_Group(bpy.types.Operator): default="Add MolecularNodes custom node group.", subtype="NONE" ) + node_link: bpy.props.BoolProperty(name = 'node_link', default = True) @classmethod def poll(cls, context): @@ -350,8 +348,8 @@ def description(cls, context, properties): def execute(self, context): try: - nodes.mol_append_node(self.node_name) - mol_add_node(self.node_name, label=self.node_label) + nodes.append(self.node_name, link = self.node_link) + MN_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.') @@ -363,15 +361,17 @@ def invoke(self, context, event): def menu_item_interface(layout_function, label, node_name, - node_description='Add custom MolecularNodes node group.'): - op=layout_function.operator('mol.add_custom_node_group', - text = label, emboss = True, depress=False) + node_description='Add custom MolecularNodes node group.', + node_link = True + ): + op = layout_function.operator('mn.add_custom_node_group', text = label) op.node_label = label op.node_name = node_name op.node_description = node_description + op.node_link = node_link -class MOL_OT_Style_Surface_Custom(bpy.types.Operator): - bl_idname = "mol.style_surface_custom" +class MN_OT_Style_Surface_Custom(bpy.types.Operator): + bl_idname = "mn.style_surface_custom" bl_label = "My Class Name" bl_description = "Create a split surface representation.\nGenerates an isosurface \ based on atomic vdw_radii. Each chain has its own separate surface \ @@ -386,18 +386,18 @@ def execute(self, context): obj = context.active_object try: node_surface = nodes.create_custom_surface( - name = 'MOL_style_surface_' + obj.name + '_split', + name = 'MN_style_surface_' + obj.name + '_split', n_chains = len(obj['chain_id_unique']) ) except: - node_surface = nodes.mol_append_node('MOL_style_surface_single') + node_surface = nodes.append('MN_style_surface_single') self.report({'WARNING'}, message = 'Unable to detect number of chains.') - mol_add_node(node_surface.name) + MN_add_node(node_surface.name) return {"FINISHED"} -class MOL_OT_Assembly_Bio(bpy.types.Operator): - bl_idname = "mol.assembly_bio" +class MN_OT_Assembly_Bio(bpy.types.Operator): + bl_idname = "mn.assembly_bio" bl_label = "Build" bl_description = "**PDB Downloaded Structures Only**\nAdds node to build \ biological assembly based on symmetry operations that are extraced from the \ @@ -424,7 +424,7 @@ def execute(self, context): data_object = data_object ) - mol_add_node(node_assembly.name) + MN_add_node(node_assembly.name) return {"FINISHED"} @@ -432,20 +432,20 @@ def menu_residues_selection_custom(layout_function): obj = bpy.context.view_layer.objects.active label = 'Res ID' op = layout_function.operator( - 'mol.residues_selection_custom', + 'mn.residues_selection_custom', text = label, emboss = True, depress = True ) def menu_item_surface_custom(layout_function, label): - op = layout_function.operator('mol.style_surface_custom', + op = layout_function.operator('mn.style_surface_custom', text = label, emboss = True, depress = True) -class MOL_OT_Custom_Color_Node(bpy.types.Operator): - bl_idname = "mol.custom_color_node" +class MN_OT_Custom_Color_Node(bpy.types.Operator): + bl_idname = "mn.custom_color_node" bl_label = "Custom color by field node." bl_options = {"REGISTER", "UNDO"} @@ -477,12 +477,12 @@ def execute(self, context): obj = context.active_object try: node_color = nodes.chain_color( - node_name = f"MOL_color_{self.node_name}_{obj.name}", + node_name = f"MN_color_{self.node_name}_{obj.name}", input_list = obj[self.node_property], field = self.field, label_prefix= self.prefix ) - mol_add_node(node_color.name) + MN_add_node(node_color.name) except: self.report({"WARNING"}, message = f"{self.node_propperty} not available for object.") return {"FINISHED"} @@ -494,14 +494,14 @@ def menu_chain_selection_custom(layout_function): obj = bpy.context.view_layer.objects.active label = 'Chain ' + str(obj.name) op = layout_function.operator( - 'mol.chain_selection_custom', + 'mn.chain_selection_custom', text = label, emboss = True, depress = True ) -class MOL_OT_Chain_Selection_Custom(bpy.types.Operator): - bl_idname = "mol.chain_selection_custom" +class MN_OT_Chain_Selection_Custom(bpy.types.Operator): + bl_idname = "mn.chain_selection_custom" bl_label = "Chain Selection" bl_description = "Create a selection based on the chains.\nThis node is built on a \ per-molecule basis, taking into account the chain_ids that were detected. If \ @@ -520,20 +520,20 @@ def poll(cls, context): def execute(self, context): obj = bpy.context.view_layer.objects.active node_chains = nodes.chain_selection( - node_name = f'MOL_sel_{self.node_name}_{obj.name}', + node_name = f'MN_select_{self.node_name}_{obj.name}', input_list = obj[self.node_property], starting_value = 0, attribute = self.field, label_prefix = self.prefix ) - mol_add_node(node_chains.name) + MN_add_node(node_chains.name) return {"FINISHED"} -class MOL_OT_Residues_Selection_Custom(bpy.types.Operator): - bl_idname = "mol.residues_selection_custom" +class MN_OT_Residues_Selection_Custom(bpy.types.Operator): + bl_idname = "mn.residues_selection_custom" bl_label = "Multiple Residue Selection" bl_description = "Create a selection based on the provided residue strings.\nThis \ node is built on a per-molecule basis, taking into account the residues that \ @@ -553,12 +553,12 @@ def poll(cls, context): def execute(self, context): obj = bpy.context.view_layer.objects.active node_residues = nodes.resid_multiple_selection( - node_name = 'MOL_sel_residues', + node_name = 'MN_select_residues', input_resid_string = self.input_resid_string, ) - mol_add_node(node_residues.name) + MN_add_node(node_residues.name) return {"FINISHED"} def invoke(self, context, event): @@ -568,14 +568,14 @@ def menu_ligand_selection_custom(layout_function): obj = bpy.context.view_layer.objects.active label = 'Ligands ' + str(obj.name) op = layout_function.operator( - 'mol.ligand_selection_custom', + 'mn.ligand_selection_custom', text = label, emboss = True, depress = True ) -class MOL_OT_Ligand_Selection_Custom(bpy.types.Operator): - bl_idname = "mol.ligand_selection_custom" +class MN_OT_Ligand_Selection_Custom(bpy.types.Operator): + bl_idname = "mn.ligand_selection_custom" bl_label = "Ligand Selection" bl_description = "Create a selection based on the ligands.\nThis node is built on \ a per-molecule basis, taking into account the chain_ids that were detected. If \ @@ -589,19 +589,19 @@ def poll(cls, context): def execute(self, context): obj = bpy.context.view_layer.objects.active node_chains = nodes.chain_selection( - node_name = 'MOL_sel_' + str(obj.name) + "_ligands", + node_name = 'MN_select_' + str(obj.name) + "_ligands", input_list = obj['ligands'], starting_value = 100, attribute = 'res_name', label_prefix = "" ) - mol_add_node(node_chains.name) + MN_add_node(node_chains.name) return {"FINISHED"} -class MOL_MT_Add_Node_Menu_Properties(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_PROPERTIES' +class MN_MT_Add_Node_Menu_Properties(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_PROPERTIES' bl_label = '' @classmethod @@ -613,8 +613,8 @@ def draw(self, context): layout.operator_context = "INVOKE_DEFAULT" # currently nothing for this menu in the panel -class MOL_MT_Add_Node_Menu_Color(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_COLOR' +class MN_MT_Add_Node_Menu_Color(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_COLOR' bl_label = '' @classmethod @@ -624,44 +624,42 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Set Color', 'MOL_color_set', + menu_item_interface(layout, 'Set Color', 'MN_color_set', "Sets a new color for the selected atoms") - menu_item_interface(layout, 'Set Color Common', 'MOL_color_set_common', - "Choose a color for the most common elements in PDB \ - structures") - menu_item_interface(layout, 'Common Elements', 'MOL_color_element_common', - "Choose a color for the most common elements in PDB \ - structures") layout.separator() - menu_item_interface(layout, 'Goodsell Colors', 'MOL_color_goodsell', + menu_item_interface(layout, 'Goodsell Colors', 'MN_color_goodsell', "Adjusts the given colors to copy the 'Goodsell Style'.\n \ Darkens the non-carbon atoms and keeps the carbon atoms \ the same color. Highlights differences without being too \ visually busy") - # menu_item_interface(layout, 'Color by B Factor', 'MOL_color_map_attribute') - menu_item_interface(layout, 'Color by Attribute', 'MOL_color_map_attribute') - menu_item_interface(layout, 'Random Color', 'MOL_color_random') layout.separator() - menu_item_interface(layout, 'Color by SS', 'MOL_color_sec_struct', - "Specify colors based on the secondary structure") - menu_item_interface(layout, 'Color by Atomic Number', 'MOL_color_atomic_number', - "Creates a color based on atomic_number field") - menu_item_interface(layout, 'Color by Element', 'MOL_color_element', + # menu_item_interface(layout, 'Color by B Factor', 'MN_color_map_attribute') + menu_item_interface(layout, 'Attribute Map', 'MN_color_attribute_map') + menu_item_interface(layout, 'Attribute Random', 'MN_color_attribute_random') + layout.separator() + menu_item_interface(layout, 'Element', 'MN_color_element', "Choose a color for each of the first 20 elements") # menu_item_color_chains(layout, 'Color by Chains') - op = layout.operator('mol.custom_color_node', text = 'Color by Chain') + op = layout.operator('mn.custom_color_node', text = 'Chain') op.node_property = 'chain_id_unique' op.node_name = "chain" op.prefix = 'Chain ' op.field = 'chain_id' - op = layout.operator('mol.custom_color_node', text = 'Color by Entity') + op = layout.operator('mn.custom_color_node', text = 'Entity') op.node_property = 'entity_names' op.node_name = "chain" op.prefix = "" op.field = 'entity_id' + menu_item_interface(layout, 'Secondary Structure', 'MN_color_sec_struct', + "Specify colors based on the secondary structure") + menu_item_interface(layout, 'Atomic Number', 'MN_color_atomic_number', + "Creates a color based on atomic_number field") + menu_item_interface(layout, 'Element Common', 'MN_color_common', + "Choose a color for the most common elements in PDB \ + structures") -class MOL_MT_Add_Node_Menu_Bonds(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_BONDS' +class MN_MT_Add_Node_Menu_Bonds(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_BONDS' bl_label = '' @classmethod @@ -671,20 +669,20 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Find Bonds', 'MOL_bonds_find', + menu_item_interface(layout, 'Find Bonds', 'MN_bonds_find', "Finds bonds between atoms based on distance.\n\ 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. Does not detect bond type") - menu_item_interface(layout, 'Break Bonds', 'MOL_bonds_break', + menu_item_interface(layout, 'Break Bonds', 'MN_bonds_break', "Will delete a bond between atoms that already exists \ based on a distance cutoff") - menu_item_interface(layout, 'Find Bonded Atoms', 'MOL_bonds_find_bonded', + menu_item_interface(layout, 'Find Bonded Atoms', 'MN_bonds_find_bonded', "Based on an initial selection, finds atoms which are \ within a certain number of bonds away") -class MOL_MT_Add_Node_Menu_Styling(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_SYLING' +class MN_MT_Add_Node_Menu_Styling(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_SYLING' bl_label = '' @classmethod @@ -694,36 +692,44 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Atoms Cycles', 'MOL_style_atoms_cycles', + menu_item_interface(layout, 'Atoms', 'MN_style_atoms', 'A sphere atom representation, visible ONLY in Cycles. \ Based on point-cloud rendering') - menu_item_interface(layout, 'Atoms EEVEE', 'MOL_style_atoms_eevee', - 'A sphere atom representation, visible in EEVEE and \ - Cycles. Based on mesh instancing which slows down viewport \ - performance') - menu_item_interface(layout, 'Cartoon', 'MOL_style_cartoon', + menu_item_interface(layout, 'Cartoon', 'MN_style_cartoon', 'Create a cartoon representation, highlighting secondary \ structure through arrows and ribbons.') - menu_item_interface(layout, 'Ribbon Protein', 'MOL_style_ribbon_protein', + menu_item_interface(layout, 'Ribbon Protein', 'MN_style_ribbon_protein', 'Create a ribbon mesh based off of the alpha-carbons of \ the structure') - menu_item_interface(layout, 'Ribbon Nucleic', 'MOL_style_ribbon_nucleic', + menu_item_interface(layout, 'Ribbon Nucleic', 'MN_style_ribbon_nucleic', 'Create a ribbon mesh and instanced cylinders for nucleic \ acids.') - menu_item_interface(layout, 'Surface', 'MOL_style_surface_single', - "Create a single joined surface representation. \ - Generates an isosurface based on atomic vdw_radii. All \ - chains are part of the same surface. Use Surface Split \ - Chains to have a single surface per chain") menu_item_surface_custom(layout, 'Surface Split Chains') - menu_item_interface(layout, 'Ball and Stick', 'MOL_style_ball_and_stick', + menu_item_interface(layout, 'Ball and Stick', 'MN_style_ball_and_stick', "A style node to create ball and stick representation. \ Icospheres are instanced on atoms and cylinders for bonds. \ Bonds can be detected if they are not present in the \ structure") + menu_item_interface(layout, 'Sticks', 'MN_style_sticks', + "Turn each bond into a cylinder mesh") + layout.separator() + layout.label(text = 'Utilities') + menu_item_interface(layout, 'Atoms Cycles', 'MN_style_atoms_cycles', + 'A sphere atom representation, visible ONLY in Cycles. \ + Based on point-cloud rendering') + menu_item_interface(layout, 'Atoms EEVEE', 'MN_style_atoms_eevee', + 'A sphere atom representation, visible in EEVEE and \ + Cycles. Based on mesh instancing which slows down viewport \ + performance') + menu_item_interface(layout, 'Surface', 'MN_style_surface_single', + "Create a single joined surface representation. \ + Generates an isosurface based on atomic vdw_radii. All \ + chains are part of the same surface. Use Surface Split \ + Chains to have a single surface per chain") + menu_item_interface(layout, 'Cartoon Utilities', 'MN_style_cartoon_utils') -class MOL_MT_Add_Node_Menu_Selections(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_SELECTIONS' +class MN_MT_Add_Node_Menu_Selections(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_SELECTIONS' bl_label = '' @classmethod @@ -733,70 +739,64 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Select Atoms', 'MOL_sel_atoms', + menu_item_interface(layout, 'Select Atoms', 'MN_select_atoms', "Separate atoms based on a selection field.\n" + "Takes atoms and splits them into the selected atoms the \ inverted atoms, based on a selection field") - menu_item_interface(layout, 'Separate Polymers', 'MOL_sel_sep_polymers', + menu_item_interface(layout, 'Separate Polymers', 'MN_separate_polymers', "Separate the Geometry into the different polymers.\n" + "Outputs for protein, nucleic & sugars") layout.separator() menu_chain_selection_custom(layout) - op = layout.operator('mol.chain_selection_custom', text = 'Chain Selection') + op = layout.operator('mn.chain_selection_custom', text = 'Chain') op.field = 'chain_id' op.prefix = 'Chain ' op.node_property = 'chain_id_unique' op.field = 'chain_id' op.node_name = 'chain' - op = layout.operator('mol.chain_selection_custom', text = 'Entity Selection') + op = layout.operator('mn.chain_selection_custom', text = 'Entity') op.field = 'entity_id' op.prefix = '' op.node_property = 'entity_names' op.field = 'entity_id' op.node_name = 'entity' menu_ligand_selection_custom(layout) + menu_item_interface(layout, 'Secondary Structure', 'MN_select_sec_struct') layout.separator() - menu_item_interface(layout, 'Backbone', 'MOL_sel_backbone', + menu_item_interface(layout, 'Backbone', 'MN_select_backbone', "Select atoms it they are part of the side chains or backbone.") - menu_item_interface(layout, 'Atom Properties', 'MOL_sel_atom_propeties', - "Create a selection based on the properties of the atom.\n\ - Fields for is_alpha_carbon, is_backbone, is_peptide, \ - is_nucleic, is_solvent and is_carb") - menu_item_interface(layout, 'Atomic Number', 'MOL_sel_atomic_number', + menu_item_interface(layout, 'Atomic Number', 'MN_select_atomic_number', "Create a selection if input value equal to the \ atomic_number field.") - menu_item_interface(layout, 'Element Name', 'MOL_sel_element_name', + menu_item_interface(layout, 'Element', 'MN_select_element', "Create a selection of particular elements by name. Only \ first 20 elements supported") layout.separator() - menu_item_interface(layout, 'Distance', 'MOL_sel_distance', - "Create a selection based on the distance to a selected \ - object.\n The cutoff is scaled based on the objects scale \ - and the 'Scale Cutoff' value.") - menu_item_interface(layout, 'Slice', 'MOL_sel_slice', - "Create a selection that is a slice along one of the XYZ \ - axes, based on the position of an object.") + menu_item_interface(layout, 'Proximity', 'MN_select_proximity', + "Select atoms within a certain proximity of some target atoms.") + menu_item_interface(layout, 'Cube', 'MN_select_cube', + "Create a selection using an Empty Cube", + node_link = False) + menu_item_interface(layout, 'Sphere', 'MN_select_sphere', + "Create a selection using an Empty Sphere", + node_link = False) layout.separator() menu_residues_selection_custom(layout) - menu_item_interface(layout, 'Res ID Single', 'MOL_sel_res_id', + menu_item_interface(layout, 'Res ID Single', 'MN_select_res_ID_single', "Create a selection if res_id matches input field") - menu_item_interface(layout, 'Res ID Range', 'MOL_sel_res_id_range', + menu_item_interface(layout, 'Res ID Range', 'MN_select_res_ID_range', "Create a selection if the res_id is within the given \ thresholds") - menu_item_interface(layout, 'Res Name Peptide', 'MOL_sel_res_name', + menu_item_interface(layout, 'Res Name Peptide', 'MN_select_res_name', "Create a selection of particular amino acids by name") - menu_item_interface(layout, 'Res Name Nucleic', 'MOL_sel_res_name_nucleic', + menu_item_interface(layout, 'Res Name Nucleic', 'MN_select_nucleic_res_name', "Create a selection of particular nucleic acids by name") - menu_item_interface(layout, 'Res Whole', 'MOL_sel_res_whole', + menu_item_interface(layout, 'Res Whole', 'MN_select_whole_res', "Expand the selection to every atom in a residue, if any \ of those atoms are in the initial selection") - menu_item_interface(layout, 'Res Atoms', 'MOL_sel_res_atoms', - "Create a selection based on the atoms of a residue.\n" + - "Selections for CA, backbone atoms (N, C, O), sidechain \ - and backbone") -class MOL_MT_Add_Node_Menu_Assembly(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_ASSEMBLY' +class MN_MT_Add_Node_Menu_Assembly(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_ASSEMBLY' bl_label = '' @classmethod @@ -806,17 +806,17 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - layout.operator("mol.assembly_bio", + layout.operator("mn.assembly_bio", text = "Biological Assembly", emboss = True, depress=True ) - menu_item_interface(layout, 'Center Assembly', 'MOL_assembly_center', + menu_item_interface(layout, 'Center Assembly', 'MN_assembly_center', "Center the structure on the world origin based on \ bounding box") -class MOL_MT_Add_Node_Menu_Membranes(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_MEMBRANES' +class MN_MT_Add_Node_Menu_Membranes(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_MEMBRANES' bl_label = '' @classmethod @@ -826,10 +826,10 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Setup Atomic Properties', 'MOL_prop_setup') + menu_item_interface(layout, 'Setup Atomic Properties', 'MN_prop_setup') -class MOL_MT_Add_Node_Menu_DNA(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_DNA' +class MN_MT_Add_Node_Menu_DNA(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_DNA' bl_label = '' @classmethod @@ -839,27 +839,27 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Double Helix', 'MOL_dna_double_helix', + menu_item_interface(layout, 'Double Helix', 'MN_dna_double_helix', "Create a DNA double helix from an input curve.\n" + "Takes an input curve and instances for the bases, returns \ instances of the bases in a double helix formation") - menu_item_interface(layout, 'Bases', 'MOL_dna_bases', + menu_item_interface(layout, 'Bases', 'MN_dna_bases', "Provide the DNA bases as instances to be styled and \ passed onto the Double Helix node") layout.separator() - menu_item_interface(layout, 'Style Atoms Cyeles', 'MOL_dna_style_atoms_cycles', + menu_item_interface(layout, 'Style Atoms Cyeles', 'MN_dna_style_atoms_cycles', "Style the DNA bases with spheres only visible in Cycles") - menu_item_interface(layout, 'Style Atoms EEVEE', 'MOL_dna_style_atoms_eevee', + menu_item_interface(layout, 'Style Atoms EEVEE', 'MN_dna_style_atoms_eevee', "Style the DNA bases with spheres visible in Cycles and \ EEVEE") - menu_item_interface(layout, 'Style Surface', 'MOL_dna_style_surface', + menu_item_interface(layout, 'Style Surface', 'MN_dna_style_surface', "Style the DNA bases with surface representation") menu_item_interface(layout, 'Style Ball and Stick', - 'MOL_dna_style_ball_and_stick', + 'MN_dna_style_ball_and_stick', "Style the DNA bases with ball and stick representation") -class MOL_MT_Add_Node_Menu_Animation(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_ANIMATION' +class MN_MT_Add_Node_Menu_Animation(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_ANIMATION' bl_label = '' @classmethod @@ -869,14 +869,14 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Animate Frames', 'MOL_animate_frames', + menu_item_interface(layout, 'Animate Frames', 'MN_animate_frames', "Interpolate between frames of a trajectory." + "Given a collection of frames for a trajectory, this node \ interpolates between them from start to finish based on \ the Animate field taking a value from 0 to 1. The \ positions of the Atoms are then moved based on this field") - menu_item_interface(layout, 'Animate Field', 'MOL_animate_field') - menu_item_interface(layout, 'Animate Value', 'MOL_animate_value', + menu_item_interface(layout, 'Animate Field', 'MN_animate_field') + menu_item_interface(layout, 'Animate Value', 'MN_animate_value', "Animates between given start and end values, based on \ the input start and end frame of the timeline. Clamped \ will limit the output to the 'To Min' and 'To Max', while \ @@ -884,23 +884,23 @@ def draw(self, context): 'Smoother Step' will ease in and out of these values, with \ default being linear interpolation") layout.separator() - menu_item_interface(layout, 'Res Wiggle', "MOL_animate_res_wiggle", + menu_item_interface(layout, 'Res Wiggle', "MN_animate_res_wiggle", "Wiggles the side chains of amino acids based on b_factor, \ adding movement to a structure.") - menu_item_interface(layout, 'Res to Curve', "MOL_animate_res_to_curve", + menu_item_interface(layout, 'Res to Curve', "MN_animate_res_to_curve", "Takes atoms and maps them along a curve, as a single \ long peptide chain.") layout.separator() - menu_item_interface(layout, 'Noise Position', 'MOL_noise_position', + menu_item_interface(layout, 'Noise Position', 'MN_noise_position', "Generate 3D noise field based on the position attribute") - menu_item_interface(layout, 'Noise Field', 'MOL_noise_field', + menu_item_interface(layout, 'Noise Field', 'MN_noise_field', "Generate a 3D noise field based on the given field") - menu_item_interface(layout, 'Noise Repeat', 'MOL_noise_repeat', + menu_item_interface(layout, 'Noise Repeat', 'MN_noise_repeat', "Generate a 3D noise field that repeats, based on the \ given field") -class MOL_MT_Add_Node_Menu_Utilities(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_UTILITIES' +class MN_MT_Add_Node_Menu_Utilities(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_UTILITIES' bl_label = '' @classmethod @@ -910,13 +910,13 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Booelean Chain', '.utils_bool_chain') - menu_item_interface(layout, 'Rotation Matrix', 'MOL_utils_rotation_matrix') - menu_item_interface(layout, 'Curve Resample', 'MOL_utils_curve_resample') - menu_item_interface(layout, 'Determine Secondary Structure', 'MOL_utils_dssp') + menu_item_interface(layout, 'Booelean Chain', '.MN_utils_bool_chain') + menu_item_interface(layout, 'Rotation Matrix', 'MN_utils_rotation_matrix') + menu_item_interface(layout, 'Curve Resample', 'MN_utils_curve_resample') + menu_item_interface(layout, 'Determine Secondary Structure', 'MN_utils_dssp') -class MOL_MT_Add_Node_Menu_Density(bpy.types.Menu): - bl_idname = 'MOL_MT_ADD_NODE_MENU_DENSITY' +class MN_MT_Add_Node_Menu_Density(bpy.types.Menu): + bl_idname = 'MN_MT_ADD_NODE_MENU_DENSITY' bl_label = '' @classmethod @@ -926,12 +926,12 @@ def poll(cls, context): def draw(self, context): layout = self.layout layout.operator_context = "INVOKE_DEFAULT" - menu_item_interface(layout, 'Style Surface', 'MOL_style_density_surface') - menu_item_interface(layout, 'Style Wire', 'MOL_style_density_wire') - menu_item_interface(layout, 'Sample Nearest Attribute', 'MOL_utils_sample_searest') + menu_item_interface(layout, 'Style Surface', 'MN_style_density_surface') + menu_item_interface(layout, 'Style Wire', 'MN_style_density_wire') + menu_item_interface(layout, 'Sample Nearest Attribute', 'MN_utils_sample_searest') -class MOL_MT_Add_Node_Menu(bpy.types.Menu): - bl_idname = "MOL_MT_ADD_NODE_MENU" +class MN_MT_Add_Node_Menu(bpy.types.Menu): + bl_idname = "MN_MT_ADD_NODE_MENU" bl_label = "Menu for Adding Nodes in GN Tree" @classmethod @@ -941,26 +941,26 @@ def poll(cls, context): def draw(self, context): layout = self.layout.column_flow(columns=1) layout.operator_context = "INVOKE_DEFAULT" - layout.menu('MOL_MT_ADD_NODE_MENU_SYLING', + layout.menu('MN_MT_ADD_NODE_MENU_SYLING', text='Style', icon_value=77) - layout.menu('MOL_MT_ADD_NODE_MENU_COLOR', + layout.menu('MN_MT_ADD_NODE_MENU_COLOR', text='Color', icon = 'COLORSET_07_VEC') - layout.menu('MOL_MT_ADD_NODE_MENU_DENSITY', icon = "LIGHTPROBE_CUBEMAP", + layout.menu('MN_MT_ADD_NODE_MENU_DENSITY', icon = "LIGHTPROBE_CUBEMAP", text = "Density") - layout.menu('MOL_MT_ADD_NODE_MENU_BONDS', + layout.menu('MN_MT_ADD_NODE_MENU_BONDS', text='Bonds', icon = 'FIXED_SIZE') - layout.menu('MOL_MT_ADD_NODE_MENU_SELECTIONS', + layout.menu('MN_MT_ADD_NODE_MENU_SELECTIONS', text='Selection', icon_value=256) - layout.menu('MOL_MT_ADD_NODE_MENU_ANIMATION', + layout.menu('MN_MT_ADD_NODE_MENU_ANIMATION', text='Animation', icon_value=409) - layout.menu('MOL_MT_ADD_NODE_MENU_ASSEMBLY', + layout.menu('MN_MT_ADD_NODE_MENU_ASSEMBLY', text='Assemblies', icon = 'GROUP_VERTEX') - layout.menu('MOL_MT_ADD_NODE_MENU_DNA', + layout.menu('MN_MT_ADD_NODE_MENU_DNA', text='DNA', icon='GP_SELECT_BETWEEN_STROKES') - layout.menu('MOL_MT_ADD_NODE_MENU_UTILITIES', + layout.menu('MN_MT_ADD_NODE_MENU_UTILITIES', text='Utilities', icon_value=92) -def mol_add_node_menu(self, context): +def MN_add_node_menu(self, context): if ('GeometryNodeTree' == bpy.context.area.spaces[0].tree_type): layout = self.layout - layout.menu('MOL_MT_ADD_NODE_MENU', text='Molecular Nodes', icon_value=88) + layout.menu('MN_MT_ADD_NODE_MENU', text='Molecular Nodes', icon_value=88) diff --git a/docs/documentation.md b/docs/documentation.md index 44f1b20a..a9e658f2 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -13,7 +13,7 @@ When importing a PDB structure, you get a set of interconnected nodes, of which ![](images/base_setup.png) -The MOL_style_color taking for input the geometry, meaning atom coordinates and properties (visible in the top left corner of your blender window). It allows different colors to be assigned based on the value of the atom type attribute. The output is a mesh of vertices (atoms) connected by edges (bonds) that can be converted to a curve. +The MN_style_color taking for input the geometry, meaning atom coordinates and properties (visible in the top left corner of your blender window). It allows different colors to be assigned based on the value of the atom type attribute. The output is a mesh of vertices (atoms) connected by edges (bonds) that can be converted to a curve. You are free to modify this setup as you wish using the *Shift+A* shortcut and going to the Molecular Nodes tab, which contains a set of nodes detailed thouroughly here. @@ -21,7 +21,7 @@ You are free to modify this setup as you wish using the *Shift+A* shortcut and g ### Atomic Properties -The `MOL_prop_setup` node associates all of the different atomic properties with their corresponding atoms in the structure. +The `MN_prop_setup` node associates all of the different atomic properties with their corresponding atoms in the structure. #### Required Inputs @@ -54,7 +54,7 @@ By default the properties are enabled, but they can be disable if required to pr - **AA_name** (integer field): integer number corresponding to the different residue names. Amino acids are numbered 1-20 based on alphabetical order. - See the `MOL_sel_AA_name` node for the corresponding order. + See the `MN_sel_AA_name` node for the corresponding order. - **atom_index** (integer field): integer number corresponding to the order of the atom within the structure file. diff --git a/docs/getting-started.md b/docs/getting-started.md index b66df349..fd0c72a8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -67,7 +67,7 @@ Combining multiple different nodes you can create protein models and complex 3D ![](images/mn-gn-workspace.png) -The starting style `MOL_style_atoms_cycles` is only visible via inside of the Cycles render engine. You can add other styles manipulate the data through other nodes, by adding them with Shift + A and navigating to the MolecularNodes panel at the bottom. There are several categories of nodes for different animations and styles. You can add the `Ribbon Protein` node, which will create a ribbon representation based on the alpha carbons in the structure. If there is not colour in the structure, ensure that the node has a material `MOL_atomic_material` at the bottom of the node. +The starting style `MN_style_atoms_cycles` is only visible via inside of the Cycles render engine. You can add other styles manipulate the data through other nodes, by adding them with Shift + A and navigating to the MolecularNodes panel at the bottom. There are several categories of nodes for different animations and styles. You can add the `Ribbon Protein` node, which will create a ribbon representation based on the alpha carbons in the structure. If there is not colour in the structure, ensure that the node has a material `MN_atomic_material` at the bottom of the node. ![](images/mn-gn-style-ribbon.png) @@ -77,4 +77,4 @@ There are many ways to quickly create animations inside of Blender and Molecular ![Quickly adding life to a crystal structure.](images/mn-wiggle-example.gif){fig-alt="A gif showing amino acids wiggling and moving about as part of a protein cystral structure. Their movement is scaled based on their experimentally determined B-factor."} -![The nodes used in the wiggle animation above.](images/mn-wiggle-nodes.png){fig-alt="A screenshot of some geometry nodes inside of Blender. The major nodes pictured are 'MOL_style_colour', 'MOL_animate_res_wiggle' and 'MOL_style_ball_and_stick' which result in an animation of amino acids wiggling about inside of a protein structure."} \ No newline at end of file +![The nodes used in the wiggle animation above.](images/mn-wiggle-nodes.png){fig-alt="A screenshot of some geometry nodes inside of Blender. The major nodes pictured are 'MN_style_colour', 'MN_animate_res_wiggle' and 'MN_style_ball_and_stick' which result in an animation of amino acids wiggling about inside of a protein structure."} \ No newline at end of file diff --git a/docs/nodes.md b/docs/nodes.md index a5be88c5..f639852a 100644 --- a/docs/nodes.md +++ b/docs/nodes.md @@ -11,7 +11,7 @@ number-depth: 3 ### Atomic Properties -The `MOL_prop_setup` node associates all of the different atomic properties with their corresponding atoms in the structure. +The `MN_prop_setup` node associates all of the different atomic properties with their corresponding atoms in the structure. #### Required Inputs @@ -44,7 +44,7 @@ By default the properties are enabled, but they can be disable if required to pr - **AA_name** (integer field): integer number corresponding to the different residue names. Amino acids are numbered 1-20 based on alphabetical order. - See the `MOL_sel_AA_name` node for the corresponding order. + See the `MN_sel_AA_name` node for the corresponding order. - **atom_index** (integer field): integer number corresponding to the order of the atom within the structure file. diff --git a/tests/snapshots/test_load/test_rcsb_1bna_ball_and_stick/1bna_ball_verts.txt b/tests/snapshots/test_load/test_rcsb_1bna_ball_and_stick/1bna_ball_verts.txt index 66616e1c..4fdfe311 100644 --- a/tests/snapshots/test_load/test_rcsb_1bna_ball_and_stick/1bna_ball_verts.txt +++ b/tests/snapshots/test_load/test_rcsb_1bna_ball_and_stick/1bna_ball_verts.txt @@ -1,100 +1,100 @@ -0.0716,0.244,0.1543 -0.1823,0.2126,0.1663 -0.2252,0.312,0.2135 -0.1812,0.1934,0.3019 -0.1211,0.2029,0.0067 -0.1229,0.1937,0.0441 -0.0472,0.1836,0.0786 -0.1596,0.1438,0.1486 -0.1821,0.1847,0.2856 -0.2089,0.175,0.164 -0.0993,0.196,0.244 -0.1843,0.1887,0.3067 -0.1833,0.2709,0.0336 -0.2056,0.2341,0.1921 -0.1247,0.2561,0.0861 -0.1514,0.1208,-0.0453 -0.2329,0.2772,0.2228 -0.2306,0.2931,0.2205 -0.2286,0.1866,0.1781 -0.1278,0.1633,0.0843 -0.0652,0.2117,0.0497 -0.1738,0.1874,0.0052 -0.1741,0.2511,0.0937 -0.2258,0.3123,0.214 -0.1756,0.2256,0.0614 -0.0827,0.1749,0.105 -0.1413,0.1958,0.2553 -0.0964,0.2172,0.1696 -0.0882,0.1761,0.2445 -0.1841,0.2714,0.0324 -0.153,0.1156,-0.0499 -0.1138,0.173,0.0844 -0.1909,0.132,-0.0363 -0.1228,0.268,0.098 -0.116,0.1811,0.0123 -0.2076,0.3172,0.2249 -0.1796,0.2463,0.2658 -0.1297,0.1118,0.1592 -0.13,0.2312,0.2105 -0.148,0.1262,-0.051 -0.1369,0.2085,-0.0643 -0.1127,0.1803,0.0109 -0.1673,0.1881,0.1397 -0.135,0.1732,0.0802 -0.1846,0.2333,0.2718 -0.148,0.2161,-0.0673 -0.2084,0.1726,0.1613 -0.2417,0.1953,0.1776 -0.1495,0.2021,-0.1006 -0.2153,0.171,0.185 -0.1994,0.2255,-0.1095 -0.1221,0.2271,-0.0712 -0.1592,0.2451,0.0922 -0.0899,0.2593,0.0097 -0.1998,0.2713,0.2174 -0.1481,0.1703,0.2912 -0.169,0.1579,-0.0252 -0.1801,0.2222,0.0262 -0.2044,0.141,0.1637 -0.1595,0.2076,-0.1029 -0.1891,0.2244,0.1968 -0.1479,0.2844,0.0476 -0.1124,0.2633,-0.0429 -0.1442,0.2405,0.1308 -0.1009,0.2479,0.1181 -0.1804,0.2104,-0.1018 -0.1465,0.2939,0.059 -0.0814,0.1405,0.1211 -0.107,0.1796,0.2616 -0.2363,0.22,0.1951 -0.1821,0.2753,0.2182 -0.1295,0.2427,0.1654 -0.0597,0.1976,0.0731 -0.0814,0.1422,0.1183 -0.1185,0.2651,-0.0216 -0.1892,0.2196,0.1997 -0.0624,0.2136,0.0511 -0.2029,0.1522,0.1744 -0.1508,0.2053,-0.1021 -0.1146,0.1807,0.0089 -0.1994,0.1438,-0.0196 -0.1239,0.2533,0.1307 -0.1662,0.2129,-0.104 -0.1296,0.1329,0.1448 -0.1319,0.1984,-0.1 -0.1908,0.2187,-0.0922 -0.1118,0.1778,0.0856 -0.0818,0.203,0.2103 -0.0981,0.2359,0.0253 -0.0938,0.1872,0.2519 -0.1143,0.2305,0.2149 -0.0853,0.2211,0.1804 -0.24,0.2024,0.1879 -0.0959,0.2721,0.1115 -0.1307,0.2502,0.1279 -0.1107,0.1474,0.1197 -0.1468,0.2122,0.0349 -0.1294,0.2148,0.2517 -0.1209,0.1938,0.0421 -0.1236,0.1319,0.15 +0.1686,0.2055,0.1354 +0.2208,0.2737,0.2242 +0.1494,0.2241,-0.1043 +0.1558,0.2469,-0.0514 +0.1062,0.2518,-0.0419 +0.1248,0.2009,0.1127 +0.1616,0.1358,0.1595 +0.1357,0.2139,0.2501 +0.1609,0.2113,0.1711 +0.1963,0.2677,0.0178 +0.1599,0.2243,0.0648 +0.2023,0.2632,0.2204 +0.2223,0.2662,0.2028 +0.2017,0.1398,0.1648 +0.1201,0.2639,-0.028 +0.1358,0.2022,-0.0333 +0.1067,0.202,0.2155 +0.2378,0.2745,0.2055 +0.159,0.2124,0.2858 +0.0963,0.2638,0.0092 +0.1428,0.212,0.2517 +0.175,0.2425,0.0583 +0.1199,0.2597,-0.0452 +0.1694,0.228,0.1003 +0.2303,0.2067,0.0072 +0.1325,0.1945,-0.0986 +0.1903,0.311,0.2291 +0.1185,0.1876,0.0843 +0.1544,0.212,0.0693 +0.2068,0.1392,-0.0186 +0.1343,0.1965,-0.096 +0.0893,0.1812,0.0862 +0.1004,0.2653,-0.016 +0.1925,0.1325,-0.0377 +0.1632,0.1341,0.1576 +0.1756,0.1931,0.1719 +0.174,0.1875,0.0014 +0.1881,0.1388,0.1616 +0.2187,0.1837,-0.0128 +0.1748,0.1576,-0.0312 +0.2003,0.2314,-0.0861 +0.1882,0.2617,0.2221 +0.0875,0.2534,0.1168 +0.1377,0.1908,0.2695 +0.1179,0.1497,0.1444 +0.1659,0.1851,0.0063 +0.1964,0.1811,0.1701 +0.1761,0.1799,0.2849 +0.0996,0.1247,-0.0761 +0.2182,0.1906,0.0029 +0.2062,0.1461,0.1674 +0.0563,0.2279,0.0321 +0.2417,0.1954,0.1788 +0.2644,0.2502,0.1922 +0.1011,0.2066,-0.0292 +0.1005,0.1168,-0.0996 +0.1964,0.1869,0.173 +0.1342,0.2051,-0.0283 +0.1599,0.1314,0.1593 +0.1707,0.1884,0.0063 +0.1336,0.2004,-0.0986 +0.1501,0.2565,0.0905 +0.2102,0.1895,0.0094 +0.134,0.1756,0.0788 +0.1649,0.1798,0.0112 +0.2072,0.1449,0.0011 +0.0988,0.2006,0.0118 +0.1999,0.2199,-0.093 +0.2261,0.1785,0.1892 +0.063,0.2056,0.0717 +0.1316,0.1797,0.2706 +0.1556,0.2465,-0.0566 +0.1262,0.1637,0.0798 +0.0704,0.2473,0.1331 +0.1678,0.1933,0.0016 +0.1846,0.2235,-0.1138 +0.1906,0.2052,0.2982 +0.1107,0.2606,-0.0408 +0.1262,0.1772,-0.0571 +0.213,0.2179,0.2015 +0.1125,0.1996,-0.0287 +0.1998,0.2631,0.2178 +0.1454,0.1255,-0.0692 +0.1446,0.219,0.0287 +0.2016,0.2223,-0.1081 +0.1958,0.251,0.1882 +0.101,0.1984,0.0118 +0.17,0.2539,0.2686 +0.1529,0.1161,-0.0509 +0.0899,0.2812,-0.0042 +0.0581,0.2094,0.1999 +0.1808,0.1984,0.0332 +0.0845,0.2577,0.1136 +0.1199,0.1862,0.117 +0.2002,0.2348,-0.0856 +0.1258,0.1995,0.1103 +0.177,0.2581,-0.0703 +0.1594,0.2169,0.2883 +0.1468,0.2145,0.2514 +0.1948,0.2591,-0.0815 diff --git a/tests/snapshots/test_load/test_rcsb_6n2y_surface_split/6n2y_surface_verts.txt b/tests/snapshots/test_load/test_rcsb_6n2y_surface_split/6n2y_surface_verts.txt index b6e94765..fb926848 100644 --- a/tests/snapshots/test_load/test_rcsb_6n2y_surface_split/6n2y_surface_verts.txt +++ b/tests/snapshots/test_load/test_rcsb_6n2y_surface_split/6n2y_surface_verts.txt @@ -1,1000 +1,1000 @@ -1.63,1.16,2.51 -1.74,1.96,2.22 -1.27,1.58,2.2 -1.66,2.02,1.98 -1.49,2.07,1.93 -1.91,1.21,1.93 -1.4,1.79,2.84 -1.91,1.81,1.3 -1.64,1.71,2.54 -1.6,1.82,0.91 -1.69,1.25,1.47 -1.89,1.91,2.74 -1.92,2.12,2.31 -1.61,1.7,2.59 -1.54,1.79,2.75 -1.58,1.98,2.78 -1.76,1.72,1.14 -1.55,1.67,0.93 -1.75,1.94,2.56 -1.62,2.04,1.07 -2.15,1.7,2.01 -1.85,1.99,1.37 -1.93,1.54,1.15 -1.32,1.99,2.14 -1.83,1.4,0.98 -1.52,1.93,0.9 -1.44,1.66,2.5 -1.68,1.86,2.37 -1.83,1.36,2.64 -1.69,1.33,1.51 -1.65,1.77,2.35 -1.43,1.66,2.26 -1.9,1.48,2.3 -1.53,1.73,2.43 -1.71,1.83,2.09 -1.53,1.9,2.55 -1.54,1.7,2.89 -1.59,1.51,2.78 -1.38,1.88,1.97 -1.7,1.97,1.6 -1.81,1.86,1.84 -1.6,1.79,0.89 -1.25,1.41,2.18 -1.95,2.19,2.1 -1.55,1.23,1.69 -1.76,1.85,0.93 -1.49,1.34,2.53 -1.57,1.48,2.82 -1.44,1.65,1.96 -1.85,1.89,0.98 -1.64,1.47,2.32 -1.88,1.97,1.63 -1.67,1.88,1.09 -2.15,1.41,2.25 -1.22,1.93,1.92 -2.09,2.02,1.96 -1.4,2.1,2.49 -1.56,1.36,2.47 -1.5,1.69,1.91 -1.59,2.03,2.72 -1.49,1.92,2.27 -1.58,1.57,2.73 -1.43,1.65,2.36 -1.84,1.46,1.35 -1.94,1.8,1.74 -1.8,1.41,2.5 -1.58,1.16,2.82 -1.64,1.64,2.19 -1.46,1.88,2.06 -1.43,1.19,2.41 -1.31,1.96,1.96 -1.6,1.66,1.19 -1.92,1.35,2.71 -1.92,1.85,1.41 -1.63,2.0,1.9 -1.89,1.39,2.32 -1.61,1.17,1.86 -1.61,1.4,2.75 -1.43,1.47,2.78 -1.77,1.87,1.4 -1.53,1.94,2.78 -1.73,1.71,2.3 -1.19,1.68,1.98 -1.45,2.18,2.33 -1.93,1.68,2.14 -1.62,1.58,1.91 -1.47,1.48,1.36 -1.24,1.33,2.6 -1.3,1.74,2.71 -1.58,1.9,1.87 -1.55,2.03,1.38 -1.56,1.73,2.01 -1.34,1.93,2.15 -1.74,1.67,1.33 -1.57,1.34,0.93 -1.49,1.16,2.04 -1.86,1.21,2.51 -1.35,1.22,2.33 -1.91,1.26,1.93 -1.7,2.11,2.06 -1.66,1.87,0.99 -1.85,1.96,1.03 -1.21,1.42,2.22 -1.63,1.87,1.42 -1.71,1.17,1.88 -1.57,1.98,1.42 -1.45,1.37,1.2 -1.74,1.86,1.72 -1.52,1.88,2.79 -1.85,1.11,2.04 -1.8,1.68,1.25 -1.54,1.55,1.31 -1.52,1.72,2.96 -2.14,2.02,2.05 -1.6,1.17,2.49 -1.79,1.6,2.41 -1.86,1.54,1.94 -1.69,1.29,1.42 -1.51,1.88,1.39 -1.3,1.56,2.45 -1.51,1.79,2.91 -1.67,1.58,2.68 -1.57,1.88,1.28 -1.57,1.17,2.12 -1.97,1.73,1.03 -1.75,2.15,2.23 -1.87,1.94,0.9 -1.65,1.68,2.65 -1.58,1.01,2.66 -1.8,1.78,0.98 -1.45,1.21,2.28 -1.65,1.78,2.3 -1.62,1.21,2.52 -1.49,1.37,2.63 -1.46,1.12,2.8 -2.03,1.4,2.42 -1.86,1.48,1.3 -1.91,1.68,2.13 -1.3,1.27,2.27 -1.64,1.71,0.9 -1.74,1.71,2.52 -1.72,1.52,1.3 -1.66,1.23,1.95 -1.65,1.72,2.0 -1.58,1.61,1.36 -2.11,1.72,2.0 -1.55,1.23,2.92 -1.77,1.37,2.57 -1.9,1.77,1.03 -1.78,1.8,1.44 -1.75,1.76,2.05 -1.67,1.8,1.29 -1.73,1.67,2.45 -1.53,1.59,0.98 -1.89,1.81,2.8 -1.7,1.12,2.32 -1.93,1.81,1.52 -1.51,1.52,1.64 -1.77,1.91,2.35 -1.3,1.67,1.98 -1.66,1.9,2.79 -1.56,2.05,1.4 -1.44,1.66,1.91 -1.58,1.62,2.33 -1.67,1.33,1.09 -1.19,1.96,1.97 -1.86,1.86,1.25 -1.51,1.37,2.4 -1.59,1.54,2.26 -1.55,1.04,2.65 -1.74,2.0,0.94 -1.93,1.43,2.23 -1.35,1.22,2.45 -1.92,1.67,1.13 -1.49,1.61,1.3 -1.74,1.27,1.41 -1.32,1.95,2.33 -1.9,1.79,0.93 -1.99,1.77,2.51 -1.45,1.57,1.73 -1.4,1.45,1.22 -1.47,1.33,2.88 -1.55,1.11,2.8 -1.73,1.64,1.23 -1.97,1.47,1.13 -1.64,1.47,2.05 -1.63,2.2,2.44 -1.58,2.25,1.98 -1.3,1.51,2.4 -1.76,1.82,2.03 -1.78,1.98,2.05 -1.55,1.75,1.04 -1.7,1.86,1.2 -1.52,2.22,2.33 -1.55,1.81,0.89 -1.58,1.84,2.8 -1.73,1.69,2.41 -1.31,1.92,1.93 -1.11,1.6,2.32 -1.44,2.19,2.23 -1.07,1.81,1.96 -1.84,1.93,2.7 -1.63,1.92,1.64 -1.36,1.85,1.94 -1.78,1.77,1.03 -1.73,1.85,2.37 -1.43,1.68,2.59 -1.66,1.27,1.81 -1.47,1.83,2.49 -1.9,1.81,0.89 -1.73,1.59,2.3 -1.73,1.9,1.36 -1.59,1.86,0.93 -1.46,1.12,2.64 -1.77,1.81,2.72 -1.44,1.16,2.66 -1.5,1.58,1.28 -2.02,1.34,2.4 -1.63,1.68,2.56 -1.37,1.91,1.98 -1.59,1.15,2.08 -1.89,1.63,1.95 -1.59,1.08,1.94 -1.82,1.44,0.92 -1.52,1.2,2.07 -2.08,1.95,2.6 -1.37,1.92,2.51 -1.17,1.53,2.32 -1.59,1.46,1.4 -1.63,1.8,2.21 -1.6,1.18,2.22 -1.63,2.26,2.22 -2.1,1.34,2.47 -2.02,1.37,2.25 -1.74,1.85,0.98 -1.81,1.41,1.13 -1.98,1.19,2.09 -1.75,1.58,2.72 -1.24,1.55,2.65 -1.64,1.19,1.76 -1.7,2.2,2.46 -1.29,1.97,2.24 -1.7,1.21,2.93 -1.78,1.85,0.98 -1.48,1.83,2.09 -2.06,2.02,2.47 -1.93,1.85,0.89 -1.74,1.62,2.62 -1.59,1.56,2.92 -1.63,1.82,2.21 -1.98,1.35,2.46 -1.45,1.52,1.3 -1.65,1.12,2.76 -1.62,1.34,1.35 -1.41,1.94,2.54 -1.49,1.43,1.41 -1.14,1.56,2.4 -1.89,1.33,2.59 -1.54,1.69,2.72 -1.76,1.8,2.16 -1.82,1.35,2.86 -1.31,1.23,2.36 -1.95,1.49,1.2 -1.64,1.45,2.13 -1.73,1.21,1.57 -1.7,1.99,1.24 -1.71,1.31,1.22 -1.93,1.39,2.48 -2.0,1.33,2.37 -1.62,1.6,2.17 -1.49,1.86,2.74 -1.13,1.56,2.33 -1.69,1.98,1.11 -1.75,1.89,1.2 -1.16,1.6,2.22 -1.52,1.53,1.84 -1.27,1.76,1.91 -2.08,1.98,2.53 -1.62,1.84,1.42 -1.62,1.13,2.78 -1.71,1.53,1.08 -1.8,1.72,1.36 -2.01,1.32,2.44 -1.25,1.62,2.04 -1.74,1.55,1.44 -1.49,1.75,1.88 -1.91,1.68,2.67 -1.11,1.92,2.01 -1.48,1.89,1.36 -1.08,1.9,2.1 -1.71,1.92,1.99 -1.63,1.43,1.4 -1.6,1.46,2.11 -1.64,1.91,1.45 -1.71,2.0,2.05 -1.69,1.91,1.06 -1.45,1.76,1.28 -2.14,1.83,2.49 -1.36,1.61,2.54 -1.87,1.61,1.09 -1.59,2.03,1.2 -1.74,1.88,1.07 -1.91,1.61,1.38 -1.69,1.47,1.71 -1.18,1.37,2.52 -2.11,1.74,2.54 -1.55,1.66,1.91 -1.74,1.88,2.27 -1.33,1.29,2.54 -1.57,1.38,0.9 -1.65,1.57,2.34 -1.68,1.71,2.01 -1.58,1.2,1.26 -1.66,1.45,1.77 -1.13,1.86,2.19 -1.82,1.67,2.28 -1.62,1.19,1.25 -1.66,2.05,1.13 -1.42,1.97,2.41 -1.17,1.87,2.43 -1.8,1.92,1.14 -1.41,1.89,2.27 -1.67,1.53,1.19 -1.76,1.61,2.46 -1.72,1.58,2.05 -1.15,1.9,1.94 -1.84,1.94,2.78 -1.52,1.33,2.78 -1.63,1.44,2.16 -2.01,1.37,2.44 -1.56,1.16,2.49 -1.82,1.81,1.4 -1.6,1.63,2.36 -1.96,1.69,1.11 -1.7,1.87,2.29 -1.69,1.94,1.2 -1.69,1.63,1.93 -1.5,1.82,2.75 -1.56,1.43,2.73 -1.63,1.0,2.66 -1.74,1.16,2.09 -1.9,1.49,2.25 -1.38,1.62,1.99 -1.45,1.5,2.78 -1.63,1.8,0.92 -1.49,1.91,1.93 -1.86,1.63,1.85 -2.17,1.72,2.28 -1.25,1.28,2.44 -1.64,1.54,1.21 -1.54,1.6,0.94 -1.77,2.11,2.31 -2.06,2.08,2.27 -1.56,1.65,2.9 -1.33,1.64,2.42 -2.0,2.19,2.1 -1.9,1.7,2.13 -1.56,1.75,2.04 -1.65,1.57,2.32 -1.23,1.31,2.36 -1.76,1.42,2.53 -1.62,1.17,2.95 -1.79,2.04,1.0 -1.71,1.14,1.67 -1.6,1.99,0.97 -1.83,1.68,2.26 -1.28,1.95,2.22 -1.89,1.36,1.94 -1.89,1.81,1.32 -1.98,1.71,2.28 -1.55,1.19,2.86 -1.53,2.02,1.31 -1.83,2.02,0.98 -1.84,1.53,1.9 -1.52,1.11,2.39 -1.56,1.42,2.2 -1.99,1.71,2.63 -1.51,1.72,1.53 -1.95,1.76,1.79 -2.13,1.84,2.55 -1.64,1.94,1.5 -1.54,1.85,2.09 -1.69,2.23,2.22 -1.39,1.86,2.72 -1.85,1.79,1.82 -1.88,2.0,1.26 -1.91,1.32,2.51 -1.67,1.73,2.38 -1.82,1.43,2.41 -1.71,1.63,2.55 -1.85,1.47,0.95 -1.71,1.93,2.47 -1.81,1.58,2.24 -1.81,1.78,1.66 -1.86,1.38,1.08 -1.63,2.24,2.29 -1.77,1.53,2.67 -1.54,1.23,2.27 -1.66,1.44,2.81 -2.13,1.81,2.42 -1.4,1.05,2.63 -1.43,2.03,1.98 -2.16,1.61,1.95 -1.53,1.23,2.0 -1.74,1.78,1.71 -1.52,1.54,2.77 -1.38,2.11,2.24 -1.49,2.25,2.07 -1.69,1.9,2.37 -2.15,1.54,2.59 -1.63,1.28,1.08 -1.51,1.84,1.64 -1.24,1.55,2.69 -1.22,1.82,1.9 -1.23,1.68,2.68 -1.77,1.72,2.01 -1.78,1.63,1.45 -1.42,1.43,1.2 -1.75,1.83,1.52 -1.76,2.11,2.39 -1.59,1.28,2.71 -1.17,1.41,2.47 -1.73,1.7,2.39 -1.49,1.92,1.45 -1.33,1.3,2.2 -2.05,1.86,2.6 -1.55,1.19,2.44 -1.51,1.92,1.29 -1.46,1.12,2.6 -1.78,1.72,1.48 -1.54,1.56,1.88 -1.54,1.09,2.75 -1.46,1.75,1.35 -1.74,1.6,1.03 -1.63,1.04,2.73 -1.51,1.64,2.94 -1.59,2.03,0.98 -2.03,1.33,2.5 -1.61,2.19,2.57 -2.1,1.96,2.19 -1.51,1.07,2.75 -1.8,1.78,1.91 -1.8,1.79,1.27 -1.85,1.85,0.88 -2.01,1.73,1.94 -1.94,1.71,1.74 -1.56,1.15,2.29 -1.22,1.97,2.01 -1.68,1.85,1.43 -1.51,1.68,1.72 -1.74,1.64,2.04 -1.49,1.65,2.36 -1.67,1.68,1.36 -1.68,1.48,1.66 -1.69,1.22,1.16 -1.67,1.6,2.55 -1.59,1.35,1.37 -1.52,1.51,1.89 -1.68,1.9,1.37 -1.56,1.37,2.19 -2.11,1.37,2.3 -1.95,1.57,1.12 -1.56,1.74,2.72 -1.85,1.69,1.93 -1.66,1.87,1.19 -1.56,1.65,2.2 -1.39,1.55,1.18 -1.71,1.91,0.9 -1.76,1.73,2.03 -1.62,1.6,2.3 -1.69,1.47,2.09 -1.53,1.89,1.91 -1.54,1.14,2.28 -1.48,1.87,1.27 -1.5,1.6,1.35 -1.68,1.58,2.52 -1.48,1.16,2.08 -1.63,1.17,2.85 -1.65,1.25,2.89 -1.4,1.9,2.36 -1.81,1.67,1.39 -1.44,1.14,2.77 -1.78,1.46,2.75 -1.56,1.63,2.54 -1.56,1.34,1.98 -1.86,1.73,1.06 -1.63,1.9,1.7 -1.59,1.59,2.91 -1.71,1.92,2.1 -1.76,1.57,1.64 -1.58,2.25,2.13 -2.12,1.78,2.1 -1.83,1.35,1.9 -1.71,2.1,2.45 -2.12,2.1,2.1 -1.64,2.02,1.94 -1.55,1.0,2.74 -1.63,1.73,1.43 -1.14,1.82,2.48 -1.97,1.48,1.19 -1.75,1.22,1.22 -1.54,2.03,1.04 -2.19,1.52,2.51 -1.46,1.61,2.89 -1.53,1.67,1.3 -1.46,1.4,1.39 -1.83,1.91,2.78 -1.83,1.45,1.91 -1.66,1.71,2.57 -1.68,1.23,2.13 -1.61,1.16,1.82 -1.51,1.83,2.64 -1.97,1.72,1.9 -1.74,1.76,1.19 -1.48,1.1,2.12 -2.07,1.98,2.57 -1.8,1.93,1.44 -1.72,2.15,2.14 -1.64,1.53,2.14 -1.62,1.18,1.2 -1.53,1.32,2.67 -1.47,1.1,2.17 -1.81,1.74,1.41 -1.86,2.01,1.38 -1.53,1.74,1.25 -1.7,1.46,1.82 -1.66,1.24,2.84 -1.78,1.68,0.97 -1.68,1.17,1.62 -1.7,1.86,1.15 -1.81,1.36,1.27 -1.77,1.52,2.63 -1.52,1.1,2.79 -1.85,1.48,1.15 -1.39,1.31,2.09 -1.65,2.24,2.02 -1.38,1.65,2.36 -1.81,1.79,1.27 -1.71,1.25,1.49 -1.77,1.92,2.31 -1.24,1.53,2.19 -1.72,1.22,2.8 -1.32,1.82,2.69 -1.64,1.36,2.04 -1.68,1.89,2.06 -1.35,1.6,1.97 -1.65,1.88,1.77 -1.79,1.71,1.98 -1.9,1.95,1.07 -1.53,1.72,1.91 -1.85,1.11,2.09 -1.97,1.38,2.09 -1.6,2.26,2.22 -1.71,1.5,2.73 -1.78,1.6,2.35 -1.49,1.8,2.47 -1.31,1.53,2.34 -1.78,1.24,1.37 -1.51,1.64,1.17 -1.76,1.8,1.53 -1.76,1.94,1.4 -1.52,1.05,2.17 -1.76,1.49,1.26 -1.22,1.56,2.46 -2.17,1.74,2.36 -1.96,1.54,1.12 -1.59,1.83,2.89 -1.65,1.83,1.31 -1.87,1.8,2.82 -1.54,2.02,1.09 -1.62,1.68,2.7 -1.49,1.34,1.04 -1.65,1.78,2.5 -1.78,1.81,1.74 -1.68,1.53,1.9 -1.75,1.33,2.73 -1.89,1.57,0.95 -1.85,1.77,1.03 -1.9,1.96,1.26 -1.58,1.73,0.88 -1.36,2.0,2.07 -1.49,1.67,0.92 -1.49,1.94,2.66 -1.43,1.64,2.04 -1.51,1.06,2.55 -1.84,2.15,2.37 -1.51,1.83,2.85 -1.68,1.86,2.01 -1.4,1.63,1.14 -1.93,1.44,2.21 -1.55,1.06,2.17 -1.55,1.45,2.35 -1.78,1.97,2.57 -1.75,1.71,2.95 -1.64,1.84,1.03 -1.69,2.04,0.9 -1.75,2.03,2.31 -1.54,1.8,2.34 -1.74,1.22,1.62 -1.66,1.56,2.7 -1.58,1.25,1.94 -1.88,1.71,2.27 -2.09,2.02,2.23 -1.43,1.34,1.97 -1.5,1.87,1.58 -1.32,1.95,2.17 -1.93,1.41,1.33 -1.74,1.63,2.54 -1.82,1.8,1.8 -1.41,1.67,2.6 -1.65,1.24,1.54 -1.72,2.19,2.52 -1.42,1.61,2.15 -1.42,1.0,2.74 -1.9,1.75,2.8 -2.1,1.97,2.42 -1.75,1.57,1.99 -1.57,2.06,1.91 -1.11,1.92,2.11 -2.13,1.86,2.56 -1.79,1.92,2.68 -1.65,2.24,2.05 -2.2,1.58,1.99 -1.75,1.95,2.03 -1.82,1.56,2.35 -1.72,1.9,2.17 -1.53,1.41,2.41 -1.5,1.01,2.53 -1.54,1.29,2.65 -1.75,1.93,1.03 -1.5,1.9,2.78 -1.48,1.16,2.13 -1.8,2.01,2.63 -1.55,1.17,2.84 -1.72,1.49,2.71 -1.44,1.7,1.35 -1.5,1.7,1.45 -1.88,1.44,2.27 -1.78,1.6,1.61 -1.69,1.89,1.06 -1.73,2.02,2.37 -1.51,1.47,1.72 -1.54,2.02,1.15 -1.65,1.27,2.99 -1.55,1.49,1.03 -2.1,1.81,2.12 -1.33,1.96,2.22 -1.61,1.87,0.9 -1.77,1.79,2.61 -1.54,1.86,2.22 -1.52,1.67,2.55 -1.42,1.76,1.03 -1.63,1.48,2.09 -1.78,2.02,1.04 -1.99,1.79,2.61 -1.87,1.72,1.54 -1.86,1.87,1.31 -1.72,1.17,1.82 -1.6,1.19,2.85 -1.17,1.93,1.96 -1.67,1.53,1.25 -1.76,1.75,2.19 -1.66,1.63,2.7 -1.97,1.54,1.08 -1.58,1.56,1.02 -1.63,1.26,2.04 -1.66,1.15,2.03 -1.83,1.86,1.73 -1.53,1.21,1.97 -1.78,1.57,2.49 -1.48,1.2,2.8 -1.95,1.76,2.09 -1.91,1.62,1.24 -1.24,1.54,2.16 -1.85,1.77,0.98 -1.7,1.25,1.24 -1.46,1.11,2.38 -1.53,1.03,2.46 -1.39,1.78,1.38 -1.35,1.39,2.72 -1.66,1.61,2.14 -1.64,2.23,2.0 -1.59,1.74,2.39 -1.23,1.63,2.63 -1.9,1.5,1.15 -1.85,1.9,2.7 -1.45,1.1,2.23 -1.64,1.96,1.46 -1.64,1.67,1.25 -1.75,1.86,1.95 -1.81,2.11,2.3 -1.53,1.68,1.25 -1.78,1.66,2.13 -1.91,1.71,2.57 -1.66,1.3,1.66 -1.36,1.9,2.17 -1.88,1.83,1.75 -1.33,1.37,2.08 -1.66,1.75,2.43 -1.26,1.55,2.72 -1.53,1.82,1.45 -1.3,1.59,2.49 -1.74,1.96,1.47 -1.87,1.62,2.54 -1.89,1.7,0.98 -2.07,1.33,2.5 -1.58,1.62,2.31 -1.69,1.77,2.72 -1.7,1.99,1.45 -1.63,1.72,2.59 +1.53,1.8,2.66 +1.86,2.04,2.07 +1.21,1.5,2.4 +1.58,2.08,2.46 +1.5,1.8,2.4 +1.76,1.61,2.37 +1.52,1.67,2.97 +1.59,1.84,1.36 +1.46,1.73,1.89 +1.75,1.52,1.23 +1.83,2.0,2.55 +1.81,2.06,2.23 +1.51,1.79,2.4 +1.42,1.95,2.12 +1.43,1.95,2.14 +1.86,1.79,1.44 +1.9,2.13,2.16 +1.6,1.71,1.13 +2.02,1.48,2.2 +1.53,1.97,1.36 +1.77,1.58,1.14 +1.56,1.94,1.9 +1.57,1.21,1.87 +1.32,1.35,2.2 +1.8,1.73,2.31 +1.79,1.52,2.18 +1.7,1.27,1.56 +1.74,1.64,2.28 +1.35,1.29,2.33 +1.82,1.4,2.06 +2.12,1.69,2.3 +1.59,1.7,2.13 +1.48,1.61,2.96 +1.5,1.64,2.47 +1.6,1.3,2.96 +1.39,1.71,2.75 +1.75,1.68,1.92 +1.63,1.85,1.64 +1.68,2.17,2.23 +2.08,1.78,2.45 +1.49,1.08,2.48 +1.68,1.87,1.39 +1.65,1.43,2.11 +1.71,1.26,2.86 +1.14,1.85,2.25 +1.56,1.92,1.1 +1.36,1.6,2.45 +1.84,1.7,1.8 +1.76,1.62,2.62 +1.34,1.6,2.38 +1.97,1.79,2.65 +1.58,1.77,2.54 +1.28,1.51,2.2 +1.21,1.88,2.17 +1.42,2.06,2.01 +1.52,1.61,2.92 +1.62,1.34,2.95 +1.33,1.3,2.34 +1.52,1.1,2.22 +1.69,1.85,1.56 +1.93,1.4,2.25 +1.89,1.92,1.51 +1.79,1.79,1.45 +2.21,1.55,2.01 +1.71,2.12,2.52 +1.19,1.79,2.56 +1.98,1.41,2.46 +1.62,2.04,1.28 +1.6,1.97,2.51 +1.88,1.46,2.26 +1.57,1.4,1.02 +1.54,1.31,2.91 +1.84,1.97,2.74 +1.56,1.77,1.39 +1.51,1.93,1.96 +1.57,1.72,1.66 +2.07,1.94,2.59 +1.6,1.86,2.51 +1.85,2.09,2.36 +1.46,1.5,2.23 +1.63,1.46,1.0 +1.76,2.09,2.41 +1.22,1.58,2.41 +2.02,1.74,2.34 +1.63,1.74,1.35 +1.7,1.58,1.99 +2.05,1.65,2.55 +1.82,1.73,0.98 +1.75,1.52,2.94 +1.49,1.02,2.79 +1.67,1.46,2.7 +1.7,2.12,2.52 +1.76,1.61,2.39 +1.6,1.97,2.78 +1.57,1.74,0.92 +1.54,1.92,1.09 +1.64,2.16,2.44 +1.59,1.11,2.19 +1.61,1.46,1.01 +1.8,1.68,2.03 +1.54,1.9,1.96 +1.78,1.46,2.55 +1.85,1.81,0.99 +1.8,1.65,1.34 +1.44,1.6,2.33 +1.96,2.15,2.07 +1.41,1.78,2.82 +1.78,1.34,1.91 +1.79,1.52,2.49 +1.8,1.57,1.17 +1.14,1.74,2.44 +1.62,1.58,2.87 +1.47,1.76,2.77 +1.54,1.6,1.04 +1.66,1.88,0.99 +1.68,1.79,2.45 +1.44,1.84,1.27 +1.91,1.72,2.48 +1.6,1.2,2.36 +1.68,1.97,1.41 +1.72,2.14,2.38 +1.74,1.64,2.26 +1.65,1.28,2.13 +1.46,1.5,2.23 +1.59,1.1,2.44 +1.82,1.29,2.34 +1.52,1.15,2.07 +1.87,1.78,2.13 +1.72,2.01,2.49 +1.86,1.69,1.14 +1.94,1.52,2.75 +1.95,1.81,1.39 +1.45,1.88,2.27 +1.49,1.3,2.09 +2.02,1.46,2.25 +1.52,1.23,2.81 +1.87,1.41,2.48 +1.55,2.0,1.01 +1.66,1.99,1.16 +1.76,1.86,2.09 +1.59,1.71,1.51 +1.8,1.62,0.95 +1.78,1.77,2.78 +1.71,1.23,1.92 +1.89,1.7,1.84 +1.57,1.39,2.34 +1.39,2.08,2.36 +1.98,2.0,2.6 +1.44,2.02,2.41 +1.14,1.85,2.24 +1.39,1.66,2.06 +1.76,1.58,1.36 +1.3,1.73,2.52 +1.75,1.97,1.14 +1.67,1.37,2.08 +1.72,1.53,1.98 +1.67,1.6,1.28 +1.53,1.79,1.03 +1.78,1.37,2.67 +1.66,2.19,2.2 +1.81,2.03,1.4 +1.73,1.3,1.44 +2.04,1.81,2.46 +1.54,1.98,1.09 +2.07,1.52,2.19 +1.58,1.56,2.11 +1.6,1.4,0.93 +1.9,1.99,1.55 +1.87,1.37,1.1 +1.93,1.87,1.36 +1.43,1.08,2.43 +1.55,1.07,2.59 +1.65,1.92,1.93 +1.43,1.93,2.34 +1.16,1.59,2.41 +1.69,1.74,2.23 +1.84,1.68,2.75 +1.5,1.75,1.43 +1.54,1.84,2.7 +1.57,1.88,2.06 +1.79,1.76,2.34 +1.3,1.79,2.28 +1.06,1.76,2.06 +1.6,1.86,2.51 +1.26,1.55,2.38 +1.86,2.07,2.33 +1.66,1.49,1.82 +1.37,1.76,2.18 +1.79,1.85,1.36 +1.37,2.05,2.38 +1.35,1.39,2.39 +1.7,1.72,1.33 +2.05,1.81,2.46 +1.53,1.97,1.04 +1.55,1.43,2.49 +1.67,1.71,0.97 +1.49,1.99,2.55 +1.54,1.59,1.25 +1.53,1.57,1.14 +1.99,1.6,2.43 +1.55,1.65,2.43 +1.42,1.72,2.87 +1.65,1.11,2.16 +1.81,1.75,1.73 +1.73,1.31,1.5 +1.73,1.28,1.15 +1.65,1.25,1.74 +2.01,2.04,2.36 +1.43,1.73,2.79 +1.22,1.77,1.96 +1.54,1.37,1.2 +1.72,1.71,2.14 +1.53,1.85,2.54 +1.63,1.75,2.39 +1.97,1.73,2.35 +1.94,1.54,2.27 +1.68,1.87,1.39 +1.59,1.19,1.82 +1.52,1.39,2.66 +1.97,1.73,2.04 +1.33,1.36,2.54 +1.59,1.35,1.02 +1.66,1.79,2.56 +1.29,1.82,1.93 +1.97,1.41,1.12 +1.57,1.88,1.08 +1.42,1.67,2.72 +2.1,2.07,2.1 +1.56,1.9,1.13 +1.71,1.71,2.88 +1.49,1.76,2.17 +1.59,2.25,1.99 +1.77,1.49,2.53 +1.42,1.62,1.14 +1.75,1.55,1.31 +1.75,1.59,1.06 +2.1,1.77,2.39 +1.68,1.45,0.94 +1.07,1.72,2.06 +1.98,1.3,2.44 +1.53,1.27,2.94 +1.86,1.76,2.17 +1.63,1.58,2.88 +1.72,2.0,2.18 +1.38,1.13,2.67 +1.46,1.44,2.76 +1.71,1.25,1.54 +1.51,1.77,1.44 +1.76,1.68,1.25 +1.82,1.29,2.17 +1.93,1.72,2.44 +1.34,1.63,2.68 +2.16,1.68,2.42 +1.09,1.67,2.13 +1.62,1.91,0.94 +1.27,1.72,1.97 +1.67,1.6,2.12 +2.08,1.9,2.53 +2.11,1.97,2.15 +1.68,1.63,1.43 +1.78,1.75,1.21 +1.68,1.92,0.91 +1.93,1.29,2.0 +2.08,1.91,2.28 +1.78,1.87,0.94 +1.2,1.9,2.43 +1.78,1.57,2.45 +1.3,1.63,2.5 +1.29,1.61,2.47 +1.52,2.23,2.11 +1.6,1.35,1.25 +1.77,1.29,2.48 +1.53,2.11,2.59 +1.92,1.86,2.34 +1.28,1.92,2.21 +1.76,1.56,1.03 +1.55,1.7,1.36 +1.87,1.94,1.06 +1.49,1.62,2.19 +1.66,2.06,2.03 +2.18,1.52,2.25 +1.65,1.67,1.99 +1.38,2.03,2.26 +1.74,1.98,2.44 +1.75,1.56,2.95 +1.56,1.35,2.42 +1.56,1.42,2.17 +1.48,1.01,2.81 +1.49,1.6,2.46 +1.32,1.78,2.58 +1.83,1.69,2.57 +1.43,1.13,2.72 +1.49,1.67,1.24 +1.53,1.68,2.71 +1.09,1.84,2.04 +1.74,1.97,1.19 +2.09,1.76,2.39 +1.91,1.69,1.3 +1.6,1.45,2.28 +1.66,1.84,1.87 +1.28,1.72,2.54 +1.54,2.14,2.56 +1.85,1.99,1.54 +1.42,1.38,2.61 +1.95,1.36,2.15 +1.87,1.36,0.92 +1.63,2.01,1.12 +1.48,1.27,2.26 +1.86,1.99,1.28 +1.81,1.82,2.38 +1.5,1.86,1.38 +1.55,1.31,2.04 +1.36,1.85,1.98 +1.61,1.45,2.28 +1.61,1.18,2.34 +1.42,1.91,2.25 +1.85,1.46,2.11 +1.43,1.37,1.99 +1.36,1.5,2.51 +2.03,1.72,2.39 +1.59,1.83,1.63 +2.15,1.44,2.23 +1.68,2.1,2.17 +1.85,1.83,1.19 +1.75,1.61,0.89 +1.66,1.81,2.25 +2.13,2.03,2.07 +1.48,1.61,2.42 +1.23,1.38,2.43 +2.09,1.87,2.4 +1.71,1.73,2.39 +1.72,1.7,2.03 +1.59,1.29,2.42 +1.66,2.08,2.56 +1.83,1.44,2.49 +1.9,1.46,1.1 +1.59,1.84,0.98 +1.54,1.13,2.36 +1.57,1.68,0.95 +1.84,1.73,2.42 +1.22,1.92,2.01 +1.79,1.21,2.52 +1.57,1.88,1.29 +1.96,1.79,2.14 +1.83,1.95,1.5 +1.61,1.8,1.03 +1.96,1.27,2.14 +1.62,1.11,2.08 +1.72,1.39,1.96 +1.99,1.86,2.59 +1.58,1.47,2.47 +1.7,1.88,1.45 +1.98,1.9,2.49 +1.7,1.57,2.13 +2.05,1.6,2.58 +1.67,1.8,2.38 +1.52,1.64,2.97 +1.81,1.68,1.69 +1.55,1.71,0.91 +1.79,1.58,2.03 +1.33,1.97,2.35 +1.91,1.32,2.47 +1.89,1.36,2.68 +1.78,1.39,0.9 +1.84,1.96,2.06 +1.86,1.58,2.03 +1.74,1.55,1.83 +1.77,1.36,1.15 +1.66,1.94,1.92 +1.79,1.5,2.21 +1.48,1.85,2.17 +1.48,1.73,2.85 +1.94,1.76,2.52 +1.61,1.6,1.03 +1.43,1.9,2.44 +2.04,1.34,2.48 1.59,1.12,2.81 -1.58,1.72,0.88 -1.76,1.2,2.88 -1.76,1.89,2.72 -1.56,1.76,2.72 -1.51,1.39,2.73 -1.38,2.13,2.1 -1.43,1.54,1.1 -1.81,1.87,2.59 -1.52,1.57,1.54 -1.5,1.86,1.24 -1.55,1.19,2.69 -1.43,1.18,2.66 -1.9,1.95,1.09 -1.79,1.7,1.19 -1.83,1.82,1.88 -1.64,1.22,1.72 -1.78,1.85,1.03 -1.65,1.92,1.67 -1.8,1.57,2.08 -1.86,1.48,1.33 -1.3,1.61,2.11 -1.49,1.61,1.66 -1.73,2.16,2.17 -1.58,2.19,2.54 -1.6,1.37,2.08 -2.14,1.4,2.51 -1.6,1.6,1.92 -1.71,1.24,2.8 -1.76,1.81,0.93 -1.86,1.68,2.67 -2.07,1.36,2.57 -1.48,1.88,2.56 -1.71,1.28,1.25 -1.32,1.63,2.26 -1.53,1.95,1.14 -1.82,1.68,1.1 -1.59,1.73,1.31 -1.47,1.33,1.96 -1.52,1.87,2.32 -2.21,1.51,2.25 -1.57,1.66,1.95 -1.4,2.03,2.52 -1.83,2.02,1.57 -1.76,1.54,2.37 -1.6,1.58,2.59 -1.68,1.79,2.89 -1.55,1.47,2.25 -1.23,1.57,2.35 -2.09,1.97,1.96 -1.05,1.71,2.06 -1.89,1.95,0.98 -1.61,1.45,2.47 -1.57,1.73,0.92 -1.41,1.79,2.87 -1.65,1.78,2.72 -1.49,1.97,1.18 -1.59,1.26,2.15 -1.35,1.11,2.69 -1.58,1.42,1.09 -1.52,1.11,2.86 -1.72,1.78,2.2 -1.81,1.63,2.78 -2.03,2.16,2.24 -1.79,2.0,2.33 -1.53,1.38,1.14 -1.54,1.05,2.61 -1.22,1.72,2.58 -1.48,1.37,1.31 -1.65,1.61,0.97 -1.19,1.9,2.43 -1.64,1.84,1.09 -1.92,1.95,2.67 -1.82,1.4,2.58 -1.93,1.4,2.01 -1.65,2.05,0.98 -1.54,1.76,2.17 -1.63,2.23,2.33 -1.71,1.06,2.64 -1.77,2.09,1.39 -1.79,1.24,1.22 -1.46,1.74,1.39 -1.53,1.86,1.41 -1.32,2.03,2.42 -1.87,1.74,1.41 -1.88,1.47,0.97 -1.67,1.21,2.94 -1.61,1.69,2.07 -1.53,1.8,0.92 -1.71,1.21,1.77 -1.54,1.56,2.74 -1.57,2.02,1.41 -1.11,1.76,2.43 -1.58,1.8,1.22 -1.47,1.9,2.15 -2.21,1.61,2.43 -1.57,1.87,1.9 -1.24,1.47,2.18 -1.37,1.56,2.24 -1.94,1.45,2.44 -1.78,1.96,2.3 -1.86,1.63,1.89 -1.84,1.9,0.93 -1.24,1.96,2.51 -1.94,1.36,2.49 -1.82,1.78,1.91 -1.7,1.26,1.09 -1.44,1.88,1.36 -1.37,1.18,2.71 -1.61,1.37,2.3 -1.95,1.87,1.95 -1.47,2.05,2.59 -1.53,1.21,2.31 -1.46,1.28,2.09 -1.88,1.39,1.39 -1.34,1.95,2.06 -1.63,1.84,1.96 -1.27,1.41,2.59 -1.51,1.97,1.07 -2.08,1.68,1.94 -1.61,1.06,2.75 -1.85,1.94,1.2 -1.57,2.26,2.06 -1.93,1.34,1.96 -1.32,1.32,2.57 -1.85,1.16,2.35 -1.1,1.7,2.32 -2.0,1.33,2.47 -1.54,1.61,1.52 -1.65,1.86,1.25 -1.54,1.24,2.48 -1.6,1.15,2.44 -1.74,1.6,2.08 -1.6,1.15,2.12 -1.64,1.61,2.52 -1.59,1.43,1.41 -1.37,1.6,1.91 -1.62,1.63,1.14 -1.9,1.95,0.98 -1.53,1.58,1.35 -1.58,1.11,2.55 -1.52,1.68,1.97 -1.62,1.75,1.31 -1.63,1.11,2.34 -1.77,2.01,2.5 -1.75,2.02,2.52 -1.53,1.57,2.7 -1.51,2.0,0.99 -1.47,1.79,1.14 -1.75,1.95,2.09 -1.44,1.36,2.64 -1.57,1.85,0.96 -1.91,1.74,2.04 -1.41,1.04,2.48 -1.76,1.63,1.31 -1.65,1.13,2.88 -1.88,1.64,2.2 -1.68,1.84,1.23 -1.51,1.06,2.6 -1.5,1.74,2.94 -1.43,1.01,2.48 -1.69,1.57,2.09 -1.66,1.58,1.23 -1.53,1.79,1.87 -1.62,1.63,2.45 -1.53,1.85,2.71 -1.98,1.48,2.05 -1.58,1.91,1.64 -1.86,1.38,1.03 -1.55,1.71,1.21 -1.54,1.2,2.41 -1.5,1.33,1.25 -1.76,2.04,1.0 -2.03,1.73,2.49 -1.96,1.45,1.25 -1.39,1.7,2.8 -1.51,1.7,1.87 -1.78,2.0,2.29 -1.72,1.58,2.27 -1.63,1.11,2.77 -1.39,1.59,1.14 -1.81,1.55,1.01 -1.73,1.86,1.95 -1.76,1.95,1.36 -1.6,1.12,2.02 -1.96,1.49,0.98 -1.08,1.68,2.03 -2.09,1.53,1.94 -1.79,1.55,1.25 -1.88,2.01,1.52 -1.72,1.69,1.35 -1.62,1.21,2.94 -2.12,1.93,2.37 -1.44,1.21,2.45 -1.74,1.9,0.88 -1.69,1.58,2.21 -1.52,1.22,2.28 -1.56,1.16,2.44 -1.65,1.18,1.95 -1.66,1.59,2.0 -1.83,2.13,2.21 -1.53,1.81,1.23 -1.78,1.97,2.51 -1.8,1.32,2.66 -1.74,1.69,1.48 -1.82,1.86,1.03 -1.67,1.75,2.9 -1.84,1.95,0.89 -1.58,1.59,1.24 -1.59,1.52,2.96 -1.74,1.13,2.3 -1.56,1.26,1.76 -1.71,1.08,2.74 -1.86,1.49,2.46 -1.71,1.68,1.25 -1.63,1.47,2.2 -1.78,1.54,1.41 -1.18,1.51,2.36 -1.75,1.34,1.39 -1.65,1.22,1.16 -1.47,0.98,2.69 -1.83,1.9,2.6 -1.62,1.26,1.09 -1.76,1.99,1.13 -1.96,1.23,2.44 -1.71,1.72,2.76 -2.03,1.37,2.57 -1.41,1.74,1.04 -1.74,1.66,1.44 -1.69,1.56,2.71 -1.22,1.95,2.13 -1.76,1.26,1.4 -1.62,1.8,2.31 -1.73,1.52,1.36 -1.43,1.23,2.25 -1.84,1.71,2.17 -1.48,1.39,1.9 -1.65,1.71,2.07 -1.48,1.78,0.98 -1.5,1.85,1.45 -1.54,1.72,0.91 -1.84,1.59,1.99 -1.84,1.34,1.3 -1.82,1.95,0.99 -1.71,1.27,1.35 -1.71,1.76,1.63 -1.77,1.81,1.57 -1.84,1.92,0.9 -1.79,1.93,1.2 -1.51,1.54,2.82 -1.98,1.44,1.23 -1.59,1.72,2.35 -2.17,1.44,2.04 -1.75,1.87,2.38 -1.69,2.06,1.2 -1.71,1.54,2.7 -1.61,1.85,2.05 -1.83,1.35,0.98 -2.0,2.02,1.94 -1.74,1.63,1.95 -1.64,1.45,2.82 -1.58,1.49,2.94 -1.79,1.47,2.58 -1.56,1.63,1.41 -2.02,1.79,2.59 -1.67,1.72,0.89 -1.59,1.8,0.92 -1.77,1.78,1.51 -1.5,1.88,2.16 -1.49,1.15,2.88 -1.44,1.94,2.43 -1.86,1.66,1.03 -1.54,1.22,1.25 -1.7,1.82,2.61 -1.92,1.24,2.48 -1.8,1.34,1.15 -1.85,1.53,2.41 -1.24,1.97,2.33 -1.37,1.1,2.77 -1.75,1.85,1.99 -1.57,1.35,1.08 -1.51,1.77,2.06 -1.51,1.86,1.64 -1.92,1.71,1.42 -1.61,1.58,2.69 -1.66,1.53,1.91 -1.48,1.65,0.94 -1.95,1.91,0.98 -1.97,1.67,2.59 -1.13,1.91,1.97 +1.76,1.79,1.69 +1.52,1.29,2.31 +1.58,1.79,2.28 +1.41,1.94,2.39 +1.79,1.9,1.96 +1.82,1.66,2.64 +1.62,1.34,0.89 +1.53,1.67,1.77 +1.35,1.38,2.56 +1.33,1.6,2.27 +1.29,1.47,2.22 +1.78,1.77,1.82 +1.8,1.89,1.09 +1.67,1.28,1.11 +1.69,1.64,1.9 +1.75,1.95,2.42 +1.66,1.18,2.27 +1.71,2.0,2.14 +1.98,1.52,2.67 +1.75,2.02,2.37 +1.94,1.74,2.2 +1.48,1.85,2.41 +1.54,1.15,2.75 +1.7,1.85,1.58 +1.63,1.55,2.12 +1.59,1.19,2.93 +1.91,1.84,1.13 +1.64,1.18,2.45 +1.6,1.77,2.81 +2.02,1.55,2.21 +1.61,1.78,2.73 +1.92,1.67,2.66 +1.57,1.17,2.86 +1.77,1.86,2.11 +1.75,1.94,1.04 +1.56,1.89,0.93 +2.16,1.52,1.96 +1.64,1.62,2.2 +1.65,1.14,2.15 +1.33,1.61,2.36 +1.64,1.47,1.79 +1.67,1.64,2.06 +1.65,1.3,2.12 +1.57,1.29,2.06 +1.96,1.75,1.31 +1.54,1.48,2.68 +1.43,1.62,1.17 +1.78,1.62,1.63 +1.48,1.49,1.25 +1.29,1.6,2.71 +1.46,1.84,1.24 +1.7,1.28,2.12 +1.99,1.77,2.17 +1.89,1.56,0.95 +1.52,1.32,2.89 +1.81,1.33,2.65 +1.59,1.42,1.99 +1.4,1.62,1.14 +1.45,1.85,1.04 +1.77,1.88,1.96 +1.51,1.44,2.34 +1.36,1.54,2.02 +1.99,1.6,2.43 +1.61,1.13,2.15 +1.53,1.55,1.65 +1.66,1.28,1.22 +1.9,1.45,1.04 +1.91,1.47,1.05 +2.06,1.76,2.32 +1.9,1.85,0.97 +1.55,1.59,1.37 +1.93,1.44,2.46 +1.24,1.89,2.55 +1.46,1.5,2.23 +1.83,1.94,1.3 +1.75,1.72,2.03 +1.5,1.76,2.22 +1.52,2.18,2.01 +1.54,1.53,1.94 +1.49,1.99,2.3 +2.04,1.44,2.54 +1.95,1.29,2.11 +1.83,1.92,1.99 +1.92,1.94,2.13 +1.58,2.08,2.46 +1.65,1.61,1.43 +1.07,1.8,2.02 +1.44,1.01,2.62 +1.92,1.49,1.03 +1.94,1.55,2.59 +1.63,1.5,2.84 +1.81,1.73,1.38 +1.57,1.54,1.17 +1.52,2.17,2.33 +1.94,1.19,2.02 +1.74,1.81,2.3 +1.46,1.86,2.41 +1.65,1.19,1.77 +1.49,1.96,1.96 +1.85,1.39,2.61 +1.93,1.94,1.37 +1.63,1.3,1.61 +2.09,2.02,2.02 +1.61,1.8,1.19 +1.65,1.76,2.49 +1.61,1.02,2.67 +1.43,1.08,2.76 +1.6,1.29,2.54 +1.64,1.34,1.23 +1.69,1.91,0.94 +1.5,1.7,1.08 +1.55,1.52,2.58 +1.94,1.4,1.15 +1.84,1.76,1.43 +1.71,1.2,1.62 +1.45,1.86,1.0 +1.59,1.28,1.15 +1.79,1.43,2.57 +1.62,1.54,1.3 +1.6,1.18,1.92 +1.63,1.98,1.94 +1.62,1.9,2.01 +1.27,1.47,2.51 +1.76,1.98,1.09 +1.75,1.63,1.41 +1.39,2.07,2.23 +1.22,1.41,2.47 +1.76,1.23,2.81 +1.14,1.8,2.29 +1.3,1.55,2.12 +1.48,2.21,2.12 +1.35,1.41,2.04 +1.63,1.64,2.37 +1.6,1.6,2.47 +1.58,1.87,1.15 +1.24,1.93,2.48 +1.75,1.51,2.22 +1.88,1.23,2.28 +1.55,1.82,2.6 +1.54,1.58,2.84 +1.98,1.29,2.43 +2.07,1.79,2.45 +1.16,1.65,2.27 +1.94,1.51,1.11 +1.65,1.54,1.05 +1.67,1.6,1.89 +1.62,2.01,0.94 +1.71,1.24,1.48 +1.73,1.29,1.38 +1.07,1.76,2.16 +2.09,1.61,1.96 +1.89,1.54,0.95 +1.55,1.58,2.93 +1.78,1.77,2.84 +1.64,1.26,2.89 +1.67,1.68,1.3 +1.75,1.88,2.27 +1.75,1.8,1.93 +1.48,1.35,1.99 +1.55,1.77,2.89 +1.73,1.57,1.05 +1.51,1.96,1.0 +1.59,1.93,1.32 +1.62,1.98,1.99 +1.76,1.73,0.9 +1.53,1.88,2.06 +1.19,1.43,2.28 +1.54,1.15,2.93 +1.77,1.97,2.37 +1.47,1.61,2.89 +1.6,1.67,2.19 +1.57,1.45,1.28 +1.79,1.37,2.62 +1.66,1.3,1.3 +1.72,1.45,2.03 +1.75,1.94,2.24 +1.75,1.39,2.82 +1.47,1.79,1.22 +1.73,1.91,2.37 +1.33,1.93,2.22 +1.57,1.12,2.34 +1.67,1.29,2.33 +1.57,1.33,1.05 +1.91,1.69,2.14 +2.12,2.02,2.05 +1.61,2.22,2.01 +1.51,1.73,1.8 +2.04,1.81,2.21 +1.55,1.18,2.1 +1.51,1.56,1.77 +1.66,1.63,2.51 +1.34,1.33,2.23 +1.95,1.56,1.1 +1.71,1.87,2.38 +1.27,1.88,2.54 +1.58,1.72,0.98 +1.78,1.78,2.69 +1.98,2.05,2.27 +1.75,1.73,1.97 +1.46,1.87,2.44 +1.3,1.73,2.4 +2.0,1.73,2.38 +1.87,1.94,2.34 +1.61,1.9,2.06 +2.01,1.5,2.31 +1.78,1.85,2.68 +1.96,1.27,2.35 +1.43,1.95,2.15 +1.69,1.49,2.13 +1.54,1.67,1.19 +1.6,1.19,2.34 +1.49,1.78,1.2 +2.02,1.58,2.62 +1.66,1.24,1.25 +1.7,1.94,2.7 +1.8,1.89,1.46 +1.85,1.42,2.51 +1.97,1.31,2.41 +1.54,1.59,2.1 +1.92,1.94,2.13 +1.59,1.59,1.98 +1.54,1.26,2.78 +1.8,1.6,2.93 +2.0,1.44,2.27 +1.28,1.93,2.01 +1.56,1.97,2.41 +1.54,1.77,2.58 +1.63,1.43,2.05 +1.6,1.07,2.64 +1.6,1.71,1.18 +1.97,1.72,2.6 +1.57,1.8,1.8 +1.75,2.01,1.0 +1.59,1.12,2.14 +1.88,1.97,1.59 +1.28,1.79,2.26 +1.91,1.7,1.33 +1.69,1.68,2.53 +1.94,1.79,2.5 +1.8,1.58,1.14 +1.82,1.62,0.95 +1.41,1.91,2.39 +1.56,1.08,2.49 +1.83,1.73,1.9 +1.52,1.08,2.77 +1.78,1.3,1.94 +1.52,1.13,2.85 +1.87,1.44,2.75 +1.84,1.92,1.04 +1.87,1.96,2.73 +1.51,1.94,1.02 +1.69,1.72,1.33 +1.61,1.3,1.69 +1.54,1.13,2.9 +1.69,2.02,2.6 +1.57,1.59,1.58 +1.58,2.17,2.44 +1.43,1.68,1.98 +1.33,1.42,2.54 +1.63,1.28,1.71 +1.52,2.19,2.23 +1.59,1.29,1.65 +1.63,1.66,2.4 +1.79,1.96,2.19 +1.8,1.74,1.35 +1.67,1.72,1.46 +1.93,1.72,1.99 +1.75,1.61,0.89 +2.12,1.54,2.48 +1.76,1.78,1.67 +1.8,1.96,2.74 +1.74,1.7,2.17 +1.41,1.28,2.24 +1.25,1.35,2.47 +1.57,1.88,1.17 +1.82,1.8,2.55 +1.89,1.89,1.21 +2.0,1.72,2.39 +1.39,1.67,2.01 +1.44,2.02,2.41 +1.53,1.75,1.31 +1.41,2.14,2.2 +1.66,1.71,1.32 +1.86,1.67,1.18 +1.55,1.18,1.94 +1.73,1.74,2.9 +1.51,1.73,2.26 +1.43,1.59,2.51 +1.38,1.96,2.31 +1.59,1.48,1.36 +1.48,2.02,2.53 +1.57,1.41,2.38 +1.81,1.93,1.54 +1.53,1.63,1.19 +1.45,1.87,1.28 +1.69,1.88,1.04 +1.66,1.82,1.45 +1.65,1.12,1.88 +1.5,1.81,1.1 +1.69,1.65,1.74 +1.75,1.8,1.97 +1.52,1.15,2.12 +1.31,1.41,2.14 +1.52,1.44,2.34 +1.66,1.76,2.41 +1.55,1.87,2.77 +1.58,1.02,2.66 +1.77,1.61,2.68 +1.51,1.57,2.35 +1.77,1.29,2.27 +1.64,1.99,1.44 +1.76,1.67,2.46 +1.96,1.73,2.3 +2.1,1.77,2.45 +1.75,1.61,1.44 +1.28,1.4,2.22 +1.76,1.94,1.45 +1.41,1.39,2.28 +1.52,1.74,2.63 +1.92,1.63,2.62 +1.32,1.52,2.66 +1.6,1.8,2.25 +1.81,1.62,1.99 +1.86,1.59,2.06 +1.47,1.23,2.34 +1.68,1.63,2.66 +1.71,1.61,1.97 +1.24,1.44,2.23 +1.97,1.7,2.61 +1.98,2.09,2.24 +1.44,1.83,1.36 +1.31,1.66,2.6 +1.81,1.64,1.14 +1.53,1.58,2.97 +1.88,1.73,2.52 +1.35,1.44,2.63 +1.58,1.63,0.94 +1.69,1.6,0.98 +1.86,1.4,1.05 +1.68,1.63,2.57 +1.79,1.6,2.28 +1.98,1.91,2.47 +1.68,1.93,2.17 +1.64,1.55,1.0 +1.6,1.18,2.36 +1.17,1.76,2.48 +1.65,1.42,0.97 +1.8,1.73,1.08 +1.16,1.87,1.97 +2.03,1.98,2.29 +1.92,1.24,2.29 +1.82,1.29,2.17 +1.52,1.62,1.32 +1.35,1.8,2.68 +1.66,1.94,1.96 +1.74,1.17,2.26 +1.44,1.76,1.02 +1.73,1.42,1.26 +1.49,1.82,2.43 +1.87,1.92,1.06 +1.62,1.21,1.91 +1.93,1.45,1.05 +1.35,1.56,2.68 +1.6,1.14,2.1 +1.52,1.31,2.46 +1.22,1.65,2.06 +1.48,1.63,2.66 +2.13,1.55,2.16 +2.02,1.74,2.41 +1.68,2.17,2.33 +1.28,1.79,2.26 +1.92,1.39,2.14 +1.4,2.11,2.21 +1.76,1.53,2.72 +1.56,1.92,1.01 +1.22,1.9,2.13 +1.82,1.56,2.07 +1.82,1.87,1.92 +1.51,1.51,1.27 +1.57,1.7,0.96 +1.35,1.61,2.29 +1.86,2.15,2.4 +1.58,1.84,2.76 +1.44,1.57,1.99 +1.44,1.3,2.48 +1.53,1.18,2.06 +1.33,1.73,2.51 +1.5,1.98,1.93 +1.68,2.18,2.27 +2.02,1.38,2.31 +1.59,1.27,2.25 +1.51,1.91,1.37 +1.43,1.93,2.37 +1.77,1.24,2.43 +1.74,1.98,2.47 +1.72,1.28,2.68 +1.16,1.64,2.12 +1.91,1.52,2.1 +1.58,1.43,2.22 +1.2,1.49,2.43 +1.85,1.37,1.26 +1.72,1.26,2.73 +1.54,1.68,1.08 +1.54,1.44,2.18 +1.5,1.46,1.36 +1.43,1.31,2.14 +1.86,1.69,1.03 +1.58,1.88,1.04 +1.79,1.7,1.29 +1.76,1.37,1.41 +1.22,1.93,2.32 +1.49,1.15,2.8 +1.6,1.86,2.51 +1.87,1.98,2.04 +1.44,1.65,2.49 +1.83,1.66,2.72 +1.79,2.0,2.7 +1.83,1.51,2.76 +1.55,1.71,1.12 +1.75,1.87,1.04 +1.91,1.48,1.07 +1.94,1.7,1.95 +1.56,1.13,2.86 +1.45,1.65,2.22 +1.59,1.69,1.04 +1.6,1.8,1.79 +1.45,1.58,1.22 +1.76,1.69,1.85 +1.48,1.61,2.67 +1.43,1.86,2.39 +1.84,1.31,2.51 +1.57,1.74,1.74 +1.78,1.35,1.09 +1.8,1.68,1.27 +1.41,1.6,2.09 +1.63,1.37,1.03 +1.56,1.87,0.98 +2.03,1.86,2.16 +1.38,1.12,2.7 +1.49,1.71,2.01 +1.68,1.54,2.07 +1.72,1.86,2.46 +1.59,1.53,1.59 +1.61,1.19,2.33 +1.57,1.53,1.19 +1.82,1.91,1.44 +1.8,1.85,1.93 +1.62,2.01,0.93 +1.56,1.4,1.06 +1.82,1.46,1.02 +2.0,2.05,2.42 +1.88,1.63,2.56 +1.7,1.88,1.13 +1.81,1.72,1.92 +1.77,1.8,1.14 +1.87,1.47,0.98 +1.94,1.92,2.57 +1.71,2.18,2.25 +1.46,1.89,1.14 +1.58,1.33,2.47 +1.46,1.55,1.95 +1.87,1.37,1.13 +1.71,1.69,1.2 +1.61,1.02,2.69 +1.79,1.99,2.22 +1.39,2.06,2.28 +1.77,1.29,2.48 +1.57,1.56,2.15 +1.56,1.9,1.14 +1.66,1.76,2.7 +1.56,1.93,1.11 +1.41,1.57,1.18 +1.5,1.7,2.54 +1.76,1.29,1.92 +1.55,1.36,1.11 +1.69,1.18,2.49 +1.79,1.49,2.13 +1.77,1.76,1.07 +1.35,1.6,2.45 +1.76,1.82,1.06 +1.19,1.47,2.41 +1.69,1.23,1.38 +1.67,1.59,1.19 +1.67,1.66,1.11 +1.49,2.13,2.14 +1.39,1.13,2.75 +1.58,1.8,0.93 +1.79,1.2,1.94 +1.52,1.76,2.78 +1.99,1.6,2.43 +1.78,1.88,0.96 +1.66,1.54,2.31 +1.33,1.64,2.16 +1.72,1.22,1.28 +1.69,1.69,2.5 +1.76,1.82,1.06 +1.76,2.02,2.35 +1.83,1.79,2.15 +1.41,1.39,2.28 +1.49,1.38,1.95 +1.78,1.69,1.03 +1.77,1.19,2.54 +1.59,1.24,1.61 +1.56,1.9,1.08 +1.7,1.66,1.27 +1.7,1.84,1.43 +1.69,1.65,1.74 +1.56,1.92,0.99 +1.49,1.94,1.36 +1.6,1.38,2.84 +1.44,1.01,2.6 +1.51,1.58,1.69 +1.89,1.59,2.52 +1.4,1.96,2.37 +1.56,1.59,2.8 +1.46,1.99,2.0 +1.71,1.45,0.94 +2.08,1.78,2.44 +1.56,1.6,2.42 +1.48,1.6,2.88 +1.66,1.55,2.74 +1.94,1.24,2.39 +1.85,1.68,1.2 +2.05,1.86,2.1 +1.91,1.74,1.08 +1.7,1.52,1.89 +1.52,1.67,2.96 +1.87,1.37,0.92 +1.48,1.68,2.87 +1.76,1.87,1.4 +1.46,1.01,2.59 +1.71,1.78,2.87 +1.81,1.13,2.11 +1.69,1.34,0.95 +1.8,1.56,1.91 +1.22,1.87,1.93 +1.55,1.61,1.0 +1.71,1.63,2.25 +1.57,1.39,0.94 +1.24,1.91,2.23 +1.53,1.72,1.82 +1.6,1.97,1.32 +1.6,1.36,2.92 +1.47,1.61,2.42 +1.75,1.72,1.0 +1.59,1.99,1.15 +1.94,1.76,2.16 +1.28,1.64,2.48 +1.49,2.13,2.14 +1.38,1.57,2.04 +1.37,1.69,2.51 +1.54,1.13,2.7 +1.53,1.73,2.59 +1.82,1.42,1.28 +1.76,1.48,2.57 +1.54,1.29,2.09 +1.77,1.98,2.33 +2.07,1.89,2.6 +1.64,1.51,1.63 +2.05,2.03,2.21 +1.36,1.65,2.55 +1.73,1.6,2.07 +1.58,1.17,2.65 +1.6,1.13,2.31 +1.85,1.16,2.14 +1.52,2.15,2.49 +1.75,1.46,0.94 +1.88,1.66,1.9 +1.91,1.52,2.32 +1.76,1.54,2.6 +1.17,1.86,2.33 +1.44,2.02,2.41 +1.92,1.67,1.33 +1.75,1.3,2.62 +1.38,1.99,2.09 +1.55,1.34,1.09 +1.75,1.95,2.44 +1.49,1.07,2.66 +1.63,1.92,2.74 +1.84,1.75,2.21 +1.66,1.25,1.38 +1.46,1.5,1.91 +1.57,1.98,1.31 +1.8,1.34,2.22 +1.19,1.79,1.97 +1.88,1.45,1.0 +1.69,1.85,1.45 +1.56,1.28,2.07 +1.63,1.12,1.88 +1.6,1.86,2.32 +1.46,1.0,2.49 +1.68,1.5,1.98 +2.03,1.46,2.57 +1.62,1.63,2.7 +1.69,1.68,1.23 +1.45,1.87,2.43 +1.37,2.0,2.22 +1.47,1.61,2.87 +1.29,1.63,2.72 +1.25,1.68,2.67 +1.82,1.8,2.82 +1.2,1.4,2.36 +1.78,1.86,2.2 +2.08,2.05,2.18 +1.86,1.65,1.4 +1.35,1.54,1.95 +1.61,1.77,2.14 +1.44,1.92,2.14 +2.01,1.55,2.21 +1.64,1.94,2.26 +1.68,1.56,2.22 +1.34,1.79,2.56 +1.94,1.79,2.54 +1.49,1.47,2.74 +1.86,1.44,1.1 +1.89,1.92,2.58 +1.59,1.4,2.27 +1.38,1.65,2.49 +1.24,1.63,2.67 +1.79,1.72,1.66 +1.88,1.37,2.7 +2.07,1.46,2.25 +1.57,1.41,2.41 diff --git a/tests/test_load.py b/tests/test_load.py index 4e857c11..fff40bcb 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -32,7 +32,7 @@ def test_rcsb_1bna_ball_and_stick(snapshot): def test_rcsb_6n2y_surface_split(snapshot): obj = mn.load.molecule_rcsb('6n2y', starting_style=1, setup_nodes = True) node_surface = mn.nodes.create_custom_surface( - name = 'MOL_style_surface_6n2y_split', + name = 'MN_style_surface_6n2y_split', n_chains = len(obj['chain_id_unique']) ) @@ -50,7 +50,7 @@ def test_rcsb_6n2y_surface_split(snapshot): node_group.links.remove(link) new_link = node_group.links.new new_link( - node_group.nodes['MOL_color_set_common'].outputs[0], + node_group.nodes['MN_color_set'].outputs[0], node_group.nodes[style_name].inputs[0] ) new_link( @@ -153,7 +153,7 @@ def test_1cd3_bio_assembly(snapshot): node_group.links.remove(link) new_link = node_group.links.new new_link( - node_group.nodes['MOL_color_set_common'].outputs[0], + node_group.nodes['MN_color_set'].outputs[0], node_group.nodes[style_name].inputs[0] ) new_link( diff --git a/tests/test_menu.py b/tests/test_menu.py index d357680e..617f515e 100644 --- a/tests/test_menu.py +++ b/tests/test_menu.py @@ -5,18 +5,18 @@ def test_menus_registered(): menu_names = ( - "MOL_MT_Add_Node_Menu_Properties", - "MOL_MT_Add_Node_Menu_Color", - "MOL_MT_Add_Node_Menu_Bonds", - "MOL_MT_Add_Node_Menu_Styling", - "MOL_MT_Add_Node_Menu_Selections", - "MOL_MT_Add_Node_Menu_Assembly", - "MOL_MT_Add_Node_Menu_Membranes", - "MOL_MT_Add_Node_Menu_DNA", - "MOL_MT_Add_Node_Menu_Animation", - "MOL_MT_Add_Node_Menu_Utilities", - "MOL_MT_Add_Node_Menu_Density", - "MOL_MT_Add_Node_Menu" + "MN_MT_Add_Node_Menu_Properties", + "MN_MT_Add_Node_Menu_Color", + "MN_MT_Add_Node_Menu_Bonds", + "MN_MT_Add_Node_Menu_Styling", + "MN_MT_Add_Node_Menu_Selections", + "MN_MT_Add_Node_Menu_Assembly", + "MN_MT_Add_Node_Menu_Membranes", + "MN_MT_Add_Node_Menu_DNA", + "MN_MT_Add_Node_Menu_Animation", + "MN_MT_Add_Node_Menu_Utilities", + "MN_MT_Add_Node_Menu_Density", + "MN_MT_Add_Node_Menu" ) menus = bpy.types.Menu.__subclasses__()