def on_bu_set_atom_clicked(self, button): self.edit_atom_number = FieldsDialogSimple( "Select atom number", fields.edit.Element(attribute_name="atom_number"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) self.edit_atom_number.run(self) atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s " % str(atom_symbol))
def __init__(self): self.atom_expression = Expression() FieldsDialogSimple.__init__( self, "Neighbor shells", fields.faulty.Expression( label_text="Atom expression (atom, graph)", attribute_name="atom_expression", history_name="atom_expression", width=250, height=150, ), (("Evaluate", RESPONSE_EVALUATE), ("Select", RESPONSE_SELECT), (gtk.STOCK_SAVE, RESPONSE_SAVE), (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)))
def do(self): edit_config = FieldsDialogSimple( "Zeobuilder configuration", context.application.configuration.create_main_field(), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) class Settings(object): pass settings = Settings() settings.__dict__ = context.application.configuration.settings edit_config.run(settings) context.application.configuration.settings = settings.__dict__
def do(self): edit_config = FieldsDialogSimple( "Zeobuilder configuration", context.application.configuration.create_main_field(), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) class Settings(object): pass settings = Settings() settings.__dict__ = context.application.configuration.settings edit_config.run(settings) context.application.configuration.settings = settings.__dict__
def on_dialog_response(self, dialog, response_id): FieldsDialogSimple.on_dialog_response(self, dialog, response_id) if self.valid: self.atom_expression.variables = ("atom", "graph") self.atom_expression.compile_as("<atom_expression>") if response_id == RESPONSE_EVALUATE: self.evaluate() self.hide = False if response_id == RESPONSE_SELECT: self.select() self.hide = True elif response_id == RESPONSE_SAVE: self.save() self.hide = False
def on_dialog_response(self, dialog, response_id): FieldsDialogSimple.on_dialog_response(self, dialog, response_id) if self.valid: self.atom_expression.variables = ("atom", "graph") self.atom_expression.compile_as("<atom_expression>") if response_id == RESPONSE_EVALUATE: self.evaluate() self.hide = False if response_id == RESPONSE_SELECT: self.select() self.hide = True elif response_id == RESPONSE_SAVE: self.save() self.hide = False
class EditSelectionFilter(Immediate): description = "Edit selection filter" menu_info = MenuInfo("default/_Select:preferences", "_Selection filter", order=(0, 3, 2, 0)) authors = [authors.toon_verstraelen] selection_filter = FieldsDialogSimple( "Selection filter", fields.group.Table(fields=[ fields.edit.CheckButton( label_text="Filter active", attribute_name="filter_active", show_popup=False, ), fields.faulty.Expression( label_text="Filter expression", attribute_name="filter_expression", show_popup=True, history_name="filter", ) ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) @staticmethod def analyze_selection(): # A) calling ancestor if not Immediate.analyze_selection(): return False # C) passed all tests: return True def do(self): self.selection_filter.run(context.application.main)
def on_set_parameters(self, menu, cell): from zeobuilder.gui.fields_dialogs import FieldsDialogSimple dialog = FieldsDialogSimple( "Set the cell parameters", CellParameters( attribute_name="cell", show_popup=False, ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) parameters = Parameters() parameters.cell = cell if dialog.run(parameters) == gtk.RESPONSE_OK: self.field.write_to_widget(self.field.convert_to_representation(parameters.cell))
def on_set_parameters(self, menu, cell): from zeobuilder.gui.fields_dialogs import FieldsDialogSimple dialog = FieldsDialogSimple( "Set the cell parameters", CellParameters( attribute_name="cell", show_popup=False, ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) parameters = Parameters() parameters.cell = cell if dialog.run(parameters) == gtk.RESPONSE_OK: self.field.write_to_widget( self.field.convert_to_representation(parameters.cell))
def on_bu_set_atom_clicked(self, button): self.edit_atom_number = FieldsDialogSimple( "Select atom number", fields.edit.Element(attribute_name="atom_number"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) self.edit_atom_number.run(self) atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s " % str(atom_symbol))
def __init__(self): self.atom_expression = Expression() FieldsDialogSimple.__init__( self, "Neighbor shells", fields.faulty.Expression( label_text="Atom expression (atom, graph)", attribute_name="atom_expression", history_name="atom_expression", width=250, height=150, ), ( ("Evaluate", RESPONSE_EVALUATE), ("Select", RESPONSE_SELECT), (gtk.STOCK_SAVE, RESPONSE_SAVE), (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) ) )
def create_dialog(self): FieldsDialogSimple.create_dialog(self) self.list_view = gtk.TreeView(self.list_store) column = gtk.TreeViewColumn("Index", gtk.CellRendererText(), text=0) column.set_sort_column_id(0) self.list_view.append_column(column) column = gtk.TreeViewColumn("Number", gtk.CellRendererText(), text=1) column.set_sort_column_id(1) self.list_view.append_column(column) column = gtk.TreeViewColumn("Atom", gtk.CellRendererText(), text=2) column.set_sort_column_id(2) self.list_view.append_column(column) column = gtk.TreeViewColumn("Value", gtk.CellRendererText(), markup=3) column.set_sort_column_id(3) self.list_view.append_column(column) for index in xrange(1, self.max_shell_size + 1): column = gtk.TreeViewColumn("Shell %i" % index, gtk.CellRendererText(), markup=index + 3) column.set_sort_column_id(index + 3) self.list_view.append_column(column) selection = self.list_view.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect("changed", self.on_selection_changed) self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.set_shadow_type(gtk.SHADOW_IN) self.scrolled_window.set_shadow_type(gtk.SHADOW_IN) self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolled_window.set_border_width(6) self.scrolled_window.add(self.list_view) self.scrolled_window.set_size_request(400, 300) self.scrolled_window.show_all() self.dialog.vbox.pack_start(self.scrolled_window)
def create_dialog(self): FieldsDialogSimple.create_dialog(self) self.list_view = gtk.TreeView(self.list_store) column = gtk.TreeViewColumn("Index", gtk.CellRendererText(), text=0) column.set_sort_column_id(0) self.list_view.append_column(column) column = gtk.TreeViewColumn("Number", gtk.CellRendererText(), text=1) column.set_sort_column_id(1) self.list_view.append_column(column) column = gtk.TreeViewColumn("Atom", gtk.CellRendererText(), text=2) column.set_sort_column_id(2) self.list_view.append_column(column) column = gtk.TreeViewColumn("Value", gtk.CellRendererText(), markup=3) column.set_sort_column_id(3) self.list_view.append_column(column) for index in xrange(1, self.max_shell_size+1): column = gtk.TreeViewColumn("Shell %i" % index, gtk.CellRendererText(), markup=index+3) column.set_sort_column_id(index+3) self.list_view.append_column(column) selection = self.list_view.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect("changed", self.on_selection_changed) self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.set_shadow_type(gtk.SHADOW_IN) self.scrolled_window.set_shadow_type(gtk.SHADOW_IN) self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolled_window.set_border_width(6) self.scrolled_window.add(self.list_view) self.scrolled_window.set_size_request(400, 300) self.scrolled_window.show_all() self.dialog.vbox.pack_start(self.scrolled_window)
class TranslateDialog(ImmediateWithMemory): description = "Apply translation" menu_info = MenuInfo("default/_Object:tools/_Transform:dialogs", "_Translate", order=(0, 4, 1, 2, 1, 2)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Translation", fields.composed.Translation( label_text="Translate with vector t", attribute_name="translation", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.translated_nodes) == 0: return False if cache.parent_of_translated_nodes is None: return False if cache.some_nodes_fixed: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.translation = Translation.identity() return result def ask_parameters(self): cache = context.application.cache last = cache.last parent = cache.parent_of_translated_nodes if isinstance(last, Vector): b = last.children[0].translation_relative_to(parent) e = last.children[1].translation_relative_to(parent) if (b is not None) and (e is not None): self.parameters.translation = Translation(e - b) else: self.parameters = self.last_parameters() if self.parameters_dialog.run(self.parameters) != gtk.RESPONSE_OK: self.parameters.clear() def do(self): for victim in context.application.cache.translated_nodes: primitive.Transform(victim, self.parameters.translation)
class ScaleUnitCell(ImmediateWithMemory): description = "Scale the unit cell and its contents" menu_info = MenuInfo("default/_Object:tools/_Unit Cell:default", "_Scale unit cell", order=(0, 4, 1, 4, 0, 4)) repeatable = False authors = [authors.toon_verstraelen] store_last_parameters = False parameters_dialog = FieldsDialogSimple( "Scale unit cell", fields.composed.CellMatrix( label_text="Cell dimensions", attribute_name="matrix", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not Immediate.analyze_selection(): return False # B) validating universe = context.application.model.universe if sum(universe.cell.active) == 0: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.matrix = context.application.model.universe.cell.matrix.copy() return result def do(self): universe = context.application.model.universe scaling = numpy.dot(self.parameters.matrix, numpy.linalg.inv(universe.cell.matrix)) primitive.SetProperty( universe, "cell", universe.cell.copy_with(matrix=self.parameters.matrix)) for child in universe.children: if isinstance(child, GLTransformationMixin) and isinstance( child.transformation, Translation): new_transformation = child.transformation.copy_with( t=numpy.dot(scaling, child.transformation.t)) primitive.SetProperty(child, "transformation", new_transformation)
class RendererConfiguration(Immediate): description = "Edit renderer configuration" menu_info = MenuInfo("default/_View:viewer", "_Configure renderer", order=(0, 2, 0, 2)) repeatable = False authors = [authors.toon_verstraelen] renderer_configuration = FieldsDialogSimple( "Renderer configuration", fields.group.Table([ fields.edit.Color( label_text="Background color", attribute_name="background_color", ), fields.edit.Color( label_text="Periodic box color", attribute_name="periodic_box_color", ), fields.edit.Color( label_text="Selection mesh color", attribute_name="selection_mesh_color", ), fields.edit.Color( label_text="Fog color", attribute_name="fog_color", ), fields.optional.CheckOptional( fields.faulty.Length( label_text="Fog depth", attribute_name="fog_depth", low=0.0, low_inclusive=False, )), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) def do(self): class Settings(object): pass settings = Settings() settings.__dict__ = context.application.configuration.settings if self.renderer_configuration.run(settings) == gtk.RESPONSE_OK: context.application.configuration.settings = settings.__dict__ context.application.scene.update_render_settings()
def run(self, max_shell_size, rows, graph): self.graph = graph self.max_shell_size = max_shell_size self.list_store = gtk.ListStore(int, int, *([str]*(max_shell_size+2))) for index, row in enumerate(rows): row += [""]*(max_shell_size-(len(row)-4)) self.list_store.append(row) response = FieldsDialogSimple.run(self, self) self.list_view.set_model(None) for column in self.list_view.get_columns(): self.list_view.remove_column(column) del self.graph del self.max_shell_size return response
def run(self, max_shell_size, rows, graph): self.graph = graph self.max_shell_size = max_shell_size self.list_store = gtk.ListStore(int, int, *([str] * (max_shell_size + 2))) for index, row in enumerate(rows): row += [""] * (max_shell_size - (len(row) - 4)) self.list_store.append(row) response = FieldsDialogSimple.run(self, self) self.list_view.set_model(None) for column in self.list_view.get_columns(): self.list_view.remove_column(column) del self.graph del self.max_shell_size return response
class RotateDialog(ImmediateWithMemory): description = "Apply rotation (dialog)" menu_info = MenuInfo("default/_Object:tools/_Transform:dialogs", "R_otate objects", order=(0, 4, 1, 2, 1, 0)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Rotation", fields.composed.Rotation( label_text="Rotate around axis n", attribute_name="rotation", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating node = context.application.cache.node if not isinstance(node, GLTransformationMixin): return False if not isinstance(node.transformation, Rotation): return False if node.get_fixed(): return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.rotation = Rotation.identity() return result def do(self): primitive.Transform(context.application.cache.node, self.parameters.rotation, after=False)
class ReflectionDialog(ImmediateWithMemory): description = "Apply reflection transformation" menu_info = MenuInfo("default/_Object:tools/_Transform:dialogs", "_Reflection", order=(0, 4, 1, 2, 1, 3)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Reflection transformation", fields.group.Table(fields=[ fields.composed.ComposedArray( FieldClass=fields.faulty.Length, array_name="c.%s", suffices=["x", "y", "z"], attribute_name="center", label_text="Point on the reflection plane.", scientific=False, ), fields.composed.ComposedArray( FieldClass=fields.faulty.Float, array_name="n.%s", suffices=["x", "y", "z"], attribute_name="normal", label_text="Normal of the reflection plane.", scientific=False, ), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.translated_nodes) == 0: return False if cache.parent_of_translated_nodes is None: return False if cache.some_nodes_fixed: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.center = numpy.zeros(3, float) result.normal = numpy.zeros(3, float) return result def ask_parameters(self): cache = context.application.cache last = cache.last next_to_last = cache.next_to_last parent = cache.parent_of_translated_nodes Plane = context.application.plugins.get_node("Plane") if isinstance(last, Plane): f = last.parent.get_frame_relative_to(parent) self.parameters.center = f * last.center self.parameters.normal = numpy.dot(f.r, last.normal) else: self.parameters = self.last_parameters() if self.parameters_dialog.run(self.parameters) != gtk.RESPONSE_OK: self.parameters.clear() def do(self): transformation = Complete.about_axis(self.parameters.center, numpy.pi, self.parameters.normal, True) for victim in context.application.cache.translated_nodes: primitive.Transform(victim, transformation)
class CameraSettings(Immediate): description = "Edit camera settings" menu_info = MenuInfo("default/_View:viewer", "_Camera settings", order=(0, 2, 0, 1)) repeatable = False authors = [authors.toon_verstraelen] viewer_configuration = FieldsDialogSimple( "Camera configuration", fields.group.HBox(fields=[ fields.group.Table([ fields.composed.Translation( label_text="Rotation center", attribute_name="rotation_center", ), fields.composed.Rotation( label_text="Model rotation", attribute_name="rotation", ), ]), fields.group.Table([ fields.composed.Translation( label_text="Eye position", attribute_name="eye", ), fields.faulty.Length( label_text="Window size", attribute_name="window_size", low=0.0, low_inclusive=False, ), fields.faulty.Length( label_text="Window depth", attribute_name="window_depth", low=0.0, low_inclusive=False, ), fields.faulty.MeasureEntry( measure="Angle", label_text="Opening angle", attribute_name="opening_angle", low=0, low_inclusive=True, high=0.5 * numpy.pi, high_inclusive=False, show_popup=False, ), ]), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(): # A) calling ancestor if not Immediate.analyze_selection(): return False # B) validating and initialising if context.application.main is None: return False # C) passed all tests: return True def do(self): camera = context.application.camera if self.viewer_configuration.run(camera) == gtk.RESPONSE_OK: context.application.scene.update_render_settings()
class SaturateHydrogensManual(ImmediateWithMemory): description = "Saturate with hydrogens (fixed number)" menu_info = MenuInfo("default/_Object:tools/_Molecular:add", "S_aturate with hydrogens (fixed number)", order=(0, 4, 1, 5, 1, 3)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Parameters for hydrogen saturation", fields.group.Table(fields=[ fields.faulty.Int( attribute_name="num_hydrogens", label_text="Number of hydrogens per atom", minimum=1, ), fields.faulty.MeasureEntry( attribute_name="valence_angle", measure="Angle", label_text="Valence angle (X-Y-H)", scientific=False, low=0.0, high=180 * deg, ), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(): return False # B) validating if len(context.application.cache.nodes) == 0: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.num_hydrogens = 2 result.valence_angle = 109.4 * deg return result def do(self): Atom = context.application.plugins.get_node("Atom") Bond = context.application.plugins.get_node("Bond") def add_hydrogens(atom): existing_bonds = list(atom.iter_bonds()) bond_length = bonds.get_length(atom.number, 1, BOND_SINGLE) num_hydrogens = self.parameters.num_hydrogens if len(existing_bonds) == 0: t = atom.transformation.t + numpy.array([0, bond_length, 0]) H = Atom(name="auto H", number=1, transformation=Translation(t)) primitive.Add(H, atom.parent) bond = Bond(name="aut H bond", targets=[atom, H]) primitive.Add(bond, atom.parent) existing_bonds.append(bond) num_hydrogens -= 1 main_direction = numpy.zeros(3, float) for bond in existing_bonds: shortest_vector = bond.shortest_vector_relative_to(atom.parent) if bond.children[1].target == atom: shortest_vector *= -1 main_direction += shortest_vector main_direction /= numpy.linalg.norm(main_direction) normal = random_orthonormal(main_direction) rotation = Rotation.from_properties( 2 * numpy.pi / float(num_hydrogens), main_direction, False) h_pos = bond_length * ( main_direction * numpy.cos(self.parameters.valence_angle) + normal * numpy.sin(self.parameters.valence_angle)) for i in range(num_hydrogens): t = atom.transformation.t + h_pos H = Atom(name="auto H", number=1, transformation=Translation(t)) primitive.Add(H, atom.parent) bond = Bond(name="aut H bond", targets=[atom, H]) primitive.Add(bond, atom.parent) h_pos = rotation * h_pos for node in context.application.cache.nodes: if isinstance(node, Atom): add_hydrogens(node)
class SketchOptions(GladeWrapper): edit_erase_filter = FieldsDialogSimple( "Edit the Erase filter", fields.faulty.Expression( label_text="Erase filter expression", attribute_name="erase_filter", show_popup=True, history_name="filter", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) def __init__(self): GladeWrapper.__init__(self, "plugins/molecular/gui.glade", "wi_sketch", "window") self.window.hide() self.init_callbacks(self.__class__) self.init_proxies([ "cb_object", "cb_vector", "cb_erase_filter", "bu_edit_erase_filter", "la_current", "bu_set_atom", "cb_bondtype", "hbox_atoms", "hbox_quickpicks", "hbox_fragments", "la_fragment", "cb_fragment" ]) self.erase_filter = Expression("True") #Initialize atom number - this can be changed anytime with the edit_atom_number dialog self.atom_number = 6 # Initialize the GUI # 1) common parts of the comboboxes def render_icon(column, cell, model, iter): if model.get_value(iter, 0) == "Fragment": cell.set_property( "pixbuf", context.application.plugins.get_node("Atom").icon) else: cell.set_property( "pixbuf", context.application.plugins.get_node( model.get_value(iter, 0)).icon) # 2) fill the objects combo box self.object_store = gtk.ListStore(str) self.object_store.append(["Atom"]) self.object_store.append(["Fragment"]) self.object_store.append(["Point"]) self.object_store.append(["Sphere"]) self.object_store.append(["Box"]) self.cb_object.set_model(self.object_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_object.pack_start(renderer_pixbuf, expand=False) self.cb_object.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_object.pack_start(renderer_text, expand=True) self.cb_object.add_attribute(renderer_text, "text", 0) self.cb_object.set_active(0) # 3) fill the vector combo box self.vector_store = gtk.ListStore(str) self.vector_store.append(["Bond"]) self.vector_store.append(["Arrow"]) self.vector_store.append(["Spring"]) self.cb_vector.set_model(self.vector_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_vector.pack_start(renderer_pixbuf, expand=False) self.cb_vector.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_vector.pack_start(renderer_text, expand=True) self.cb_vector.add_attribute(renderer_text, "text", 0) self.cb_vector.set_active(0) # 4) fill the bond type combo box self.bondtype_store = gtk.ListStore(str, int) self.bondtype_store.append(["Single bond", BOND_SINGLE]) self.bondtype_store.append(["Double bond", BOND_DOUBLE]) self.bondtype_store.append(["Triple bond", BOND_TRIPLE]) self.bondtype_store.append(["Hybrid bond", BOND_HYBRID]) self.bondtype_store.append(["Hydrogen bond", BOND_HYDROGEN]) self.cb_bondtype.set_model(self.bondtype_store) #no icons like the others, just text here renderer_text = gtk.CellRendererText() self.cb_bondtype.pack_start(renderer_text, expand=True) self.cb_bondtype.add_attribute(renderer_text, "text", 0) self.cb_bondtype.set_active(0) # register quick pick config setting config = context.application.configuration config.register_setting( "sketch_quickpicks", [6, 7, 8, 9, 10, 11], DialogFieldInfo( "Sketch tool", (0, 2), fields.faulty.IntegerList( label_text="Quick pick atoms (applies after restart)", attribute_name="sketch_quickpicks", )), ) # 5)create the "quick pick" atom buttons for index in xrange(len(config.sketch_quickpicks)): atomnumber = config.sketch_quickpicks[index] bu_element = gtk.Button("") bu_element.set_label("%s" % periodic[atomnumber].symbol) bu_element.connect("clicked", self.on_bu_element_clicked, index) # add to hbox self.hbox_quickpicks.pack_start(bu_element) bu_element.show() # 6)fill the fragment combo box with filenames from share/fragments fragment_dir = context.get_share_filename('fragments') self.fragment_store = gtk.ListStore(str) for filename in sorted(os.listdir(fragment_dir)): # Ignore subfolders and files with extension other than cml if os.path.isdir(os.path.join(fragment_dir, filename)) or filename[-3:] != 'cml': continue self.fragment_store.append([filename[:-4]]) self.cb_fragment.set_model(self.fragment_store) renderer_text = gtk.CellRendererText() self.cb_fragment.pack_start(renderer_text, expand=True) self.cb_fragment.add_attribute(renderer_text, "text", 0) self.cb_fragment.set_active(0) @property def current_object(self): return self.object_store.get_value(self.cb_object.get_active_iter(), 0) @property def current_fragment(self): return self.fragment_store.get_value( self.cb_fragment.get_active_iter(), 0) def on_window_delete_event(self, window, event): self.window.hide() return True def on_bu_edit_erase_filter_clicked(self, button): self.edit_erase_filter.run(self) def on_bu_set_atom_clicked(self, button): self.edit_atom_number = FieldsDialogSimple( "Select atom number", fields.edit.Element(attribute_name="atom_number"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) self.edit_atom_number.run(self) atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s " % str(atom_symbol)) def on_cb_object_changed(self, combo): self.hbox_atoms.hide() self.la_current.hide() self.hbox_fragments.hide() if (self.current_object == "Atom"): self.hbox_atoms.show() self.la_current.show() if (self.current_object == "Fragment"): self.cb_fragment.show() self.hbox_fragments.show() def on_bu_element_clicked(self, widget, index): self.atom_number = context.application.configuration.sketch_quickpicks[ index] atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s" % str(atom_symbol)) def on_cb_vector_changed(self, combo): # When the selected object is an atom, show the extra button. if (self.vector_store.get_value(self.cb_vector.get_active_iter(), 0) == "Bond"): self.cb_bondtype.show() else: self.cb_bondtype.hide() def get_new(self, position): if self.current_object == "Fragment": filename = context.get_share_filename('fragments/%s.cml' % self.current_fragment) molecule = load_cml(filename)[0] Frame = context.application.plugins.get_node("Frame") new = Frame(name=self.current_fragment) load_filter = context.application.plugins.get_load_filter('cml') load_filter.load_molecule(new, molecule) else: NewClass = context.application.plugins.get_node( self.current_object) new = NewClass() if self.current_object == "Atom": new.set_number(self.atom_number) new.set_name(periodic[self.atom_number].symbol) translation = Translation(position) primitive.Transform(new, translation) return new def add_new(self, position, parent): new = self.get_new(position) if self.current_object == "Fragment": result = new.children[1] primitive.Add(new, parent) UnframeAbsolute = context.application.plugins.get_action( "UnframeAbsolute") UnframeAbsolute([new]) #return result else: primitive.Add(new, parent) return new def replace(self, gl_object): if not gl_object.get_fixed(): new = self.get_new(gl_object.transformation.t) # select a target object, i.e. the one the will be connected with # the bonds/vectors/... of the replaced object if (self.current_object == "Fragment"): # fragments are inserted as frames # take atom with index 1 as target target_object = new.children[1] else: target_object = new # check if all connections to the replaced object are applicable # to the new object. if not, then do not perform the replacement # and return early. for reference in gl_object.references[::-1]: if not reference.check_target(target_object): return # add the new object parent = gl_object.parent primitive.Add(new, parent) if (self.current_object == "Fragment"): # Fix the rotation and translation of the molecular fragment. # Rotation Bond = context.application.plugins.get_node("Bond") if len(gl_object.references) == 1 and isinstance( gl_object.references[0].parent, Bond): bond1 = gl_object.references[0].parent direction1 = bond1.shortest_vector_relative_to(parent) if bond1.children[0].target != gl_object: direction1 *= -1 bond2 = new.children[0].references[0].parent direction2 = bond2.shortest_vector_relative_to(parent) if bond2.children[0].target != target_object: direction2 *= -1 axis = numpy.cross(direction2, direction1) if numpy.linalg.norm(axis) < 1e-8: axis = random_orthonormal(direction1) angle = compute_angle(direction1, direction2) rotation = Rotation.from_properties(angle, axis, False) primitive.Transform(new, rotation) else: bond1 = None # Tranlsation pos_old = new.children[1].get_frame_relative_to(parent).t pos_new = gl_object.transformation.t translation = Translation(pos_new - pos_old) primitive.Transform(new, translation) if bond1 != None: # bond length old_length = numpy.linalg.norm(direction1) new_length = bonds.get_length( new.children[1].number, bond1.get_neighbor(gl_object).number) translation = Translation(-direction1 / old_length * (new_length - old_length)) primitive.Transform(new, translation) # let the references to the replaced object point to the new object for reference in gl_object.references[::-1]: try: primitive.SetTarget(reference, target_object) except primitive.PrimitiveError: primitive.Delete(reference.parent) # delete the replaced object primitive.Delete(gl_object) if (self.current_object == "Fragment"): # Delete the first atom in the fragment primitive.Delete(new.children[0]) # Unframe the fragment UnframeAbsolute = context.application.plugins.get_action( "UnframeAbsolute") UnframeAbsolute([new]) def connect(self, gl_object1, gl_object2): try: new = context.application.plugins.get_node( self.vector_store.get_value( self.cb_vector.get_active_iter(), 0))(targets=[gl_object1, gl_object2]) except TargetError: return if (self.vector_store.get_value(self.cb_vector.get_active_iter(), 0) == "Bond"): new.set_bond_type( self.bondtype_store.get_value( self.cb_bondtype.get_active_iter(), 1)) primitive.Add(new, common_parent([gl_object1.parent, gl_object2.parent])) def erase_at(self, p, parent): for node in context.application.main.drawing_area.iter_hits( (p[0] - 2, p[1] - 2, p[0] + 2, p[1] + 2)): try: match = (node is not None and node != parent and node.is_indirect_child_of(parent) and node.model == context.application.model and (not self.cb_erase_filter.get_active() or self.erase_filter(node))) except Exception: raise UserError( "An exception occured while evaluating the erase filter expression." ) if match: primitive.Delete(node) def tool_draw(self, p1, p2): drawing_area = context.application.main.drawing_area r1 = drawing_area.screen_to_camera(p1) r2 = drawing_area.screen_to_camera(p2) context.application.vis_backend.tool("line", r1, r2) def tool_special(self, p1, p2): pass def move_special(self, gl_object1, gl_object2, p1, p2): pass def click_special(self, gl_object): pass
class RoundRotation(Immediate): description = "Round rotation" menu_info = MenuInfo("default/_Object:tools/_Transform:special", "_Round rotation", order=(0, 4, 1, 2, 5, 0)) authors = [authors.toon_verstraelen] axes = { "x": numpy.array([1, 0, 0], float), "y": numpy.array([0, 1, 0], float), "z": numpy.array([0, 0, 1], float) } select_quaternion = FieldsDialogSimple( "Select a rotation", fields.edit.List(columns=[("Fit", "cost_function"), ("Rotation", "name")], attribute_name="quaternion", show_popup=False), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.nodes) == 0 or len(cache.nodes) > 2: return False for node in cache.nodes: if not isinstance(node, GLTransformationMixin): return False if not isinstance(node.transformation, Rotation): return False if node.get_fixed(): return False # C) passed all tests: return True def do(self): class Record(object): def __init__(self, name, quaternion): self.name = name self.quaternion = quaternion self.cost_function = 0.0 rounded_quaternions = [] cache = context.application.cache if len(cache.nodes) == 1: victim = cache.last master = None factor, selected_quaternion = rotation_matrix_to_quaternion( victim.transformation.r) elif len(cache.nodes) == 2: master = cache.last victim = cache.next_to_last factor, selected_quaternion = rotation_matrix_to_quaternion( victim.get_frame_relative_to(master).r) step = 15 for axis_name, axis in self.axes.iteritems(): for angle_index in xrange(1, 360 / step): angle = angle_index * step name = "%s (%s)" % (axis_name, angle) rad = angle * numpy.pi / 360 quaternion = numpy.concatenate( ([numpy.cos(rad)], numpy.sin(rad) * axis), 1) rounded_quaternions.append(Record(name, quaternion)) new_quaternions = [ Record("Identity", numpy.array([1.0, 0.0, 0.0, 0.0])) ] for record1 in rounded_quaternions: for record2 in rounded_quaternions: if record1.name[0] != record2.name[0]: new_quaternions.append( Record( "%s after %s" % (record1.name, record2.name), quaternion_product(record1.quaternion, record2.quaternion))) def filter_out_high_cost(records): for record in records: #print selected_quaternion, record.quaternion cosine = numpy.dot(selected_quaternion, record.quaternion) if cosine > 1: cosine = 1 elif cosine < -1: cosine = -1 cost_function = int(numpy.arccos(cosine) * 180.0 / numpy.pi) if cost_function < 10: record.cost_function = cost_function else: record.quaternion = None return filter(lambda record: record.quaternion is not None, records) new_quaternions = filter_out_high_cost(new_quaternions) for index1, record1 in enumerate(new_quaternions): if record1.quaternion is not None: for record2 in new_quaternions[:index1]: if record2.quaternion is not None: if 1 - numpy.dot(record1.quaternion, record2.quaternion) < 1e-3: record2.quaternion = None for record2 in rounded_quaternions: if 1 - numpy.dot(record1.quaternion, record2.quaternion) < 1e-3: record1.quaternion = None break new_quaternions = filter(lambda record: record.quaternion is not None, new_quaternions) rounded_quaternions = filter_out_high_cost(rounded_quaternions) rounded_quaternions.extend(new_quaternions) if len(rounded_quaternions) == 0: raise UserError("No similar rounded rotations found.") rounded_quaternions.sort(key=(lambda x: x.cost_function)) self.select_quaternion.main_field.records = rounded_quaternions user_record = Record("", rounded_quaternions[0].quaternion) if self.select_quaternion.run(user_record) != gtk.RESPONSE_OK: raise CancelException if user_record.quaternion is not None: if len(cache.nodes) == 1: r = factor * quaternion_to_rotation_matrix( user_record.quaternion) new_transformation = victim.transformation.copy_with(r=r) primitive.SetProperty(victim, "transformation", new_transformation) elif len(cache.nodes) == 2: r = numpy.dot( master.get_frame_relative_to(victim).r, factor * quaternion_to_rotation_matrix(user_record.quaternion), ) old_transformation = victim.transformation.copy_with(r=r) primitive.SetProperty(victim, "transformation", old_transformation, done=True)
class SuperCell(ImmediateWithMemory): description = "Convert the unit cell to larger unit cell" menu_info = MenuInfo("default/_Object:tools/_Unit Cell:default", "_Super cell", order=(0, 4, 1, 4, 0, 1)) authors = [authors.toon_verstraelen] store_last_parameters = False parameters_dialog = FieldsDialogSimple( "Super cell", fields.group.Table( fields=[ fields.faulty.Int( attribute_name="repetitions_%s" % ridge.lower(), label_text=ridge, minimum=1, ) for ridge in ["a", "b", "c"] ], label_text="The number of repetitions along each active axis."), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating universe = context.application.model.universe if sum(universe.cell.active) == 0: return False if hasattr(parameters, "repetitions_a") and not universe.cell.active[0]: return False if hasattr(parameters, "repetitions_b") and not universe.cell.active[1]: return False if hasattr(parameters, "repetitions_c") and not universe.cell.active[2]: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() universe = context.application.model.universe if universe.cell.active[0]: result.repetitions_a = universe.repetitions[0] if universe.cell.active[1]: result.repetitions_b = universe.repetitions[1] if universe.cell.active[2]: result.repetitions_c = universe.repetitions[2] return result def do(self): # create the repetitions vector repetitions = [] if hasattr(self.parameters, "repetitions_a"): repetitions.append(self.parameters.repetitions_a) else: repetitions.append(1) if hasattr(self.parameters, "repetitions_b"): repetitions.append(self.parameters.repetitions_b) else: repetitions.append(1) if hasattr(self.parameters, "repetitions_c"): repetitions.append(self.parameters.repetitions_c) else: repetitions.append(1) repetitions = numpy.array(repetitions, int) # serialize the positioned children universe = context.application.model.universe positioned = [ node for node in universe.children if (isinstance(node, GLTransformationMixin) and isinstance(node.transformation, Translation)) ] if len(positioned) == 0: return serialized = StringIO.StringIO() dump_to_file(serialized, positioned) # create the replica's # replicate the positioned objects new_children = {} for cell_index in iter_all_positions(repetitions): cell_index = numpy.array(cell_index) cell_hash = tuple(cell_index) serialized.seek(0) nodes = load_from_file(serialized) new_children[cell_hash] = nodes for node in nodes: t = node.transformation.t + numpy.dot(universe.cell.matrix, cell_index) new_transformation = node.transformation.copy_with(t=t) node.set_transformation(new_transformation) # forget about serialized stuff serialized.close() del serialized new_connectors = [] # replicate the objects that connect these positioned objects for cell_index in iter_all_positions(repetitions): cell_index = numpy.array(cell_index) cell_hash = tuple(cell_index) for connector in universe.children: # Only applicable to ReferentMixin with only SpatialReference # children if not isinstance(connector, ReferentMixin): continue skip = False for reference in connector.children: if not isinstance(reference, SpatialReference): skip = True break if skip: continue # first locate the new first target for this cell_index first_target_orig = connector.children[0].target first_target_index = positioned.index(first_target_orig) first_target = new_children[cell_hash][first_target_index] assert first_target is not None new_targets = [first_target] for reference in connector.children[1:]: # then find the other new targets, taking into account # periodicity other_target_orig = reference.target shortest_vector = universe.shortest_vector( (other_target_orig.transformation.t - first_target_orig.transformation.t)) translation = first_target.transformation.t + shortest_vector other_target_pos = translation other_cell_index = numpy.floor( universe.cell.to_fractional(other_target_pos)).astype( int) other_cell_index %= repetitions other_cell_hash = tuple(other_cell_index) other_target_index = positioned.index(other_target_orig) other_cell_children = new_children.get(other_cell_hash) assert other_cell_children is not None other_target = other_cell_children[other_target_index] assert other_target is not None new_targets.append(other_target) state = connector.__getstate__() state["targets"] = new_targets new_connectors.append(connector.__class__(**state)) # remove the existing nodes while len(universe.children) > 0: primitive.Delete(universe.children[0]) del positioned # multiply the cell matrix and reset the number of repetitions new_matrix = universe.cell * repetitions primitive.SetProperty(universe, "cell", new_matrix) primitive.SetProperty(universe, "repetitions", numpy.array([1, 1, 1], int)) # add the new nodes for nodes in new_children.itervalues(): for node in nodes: primitive.Add(node, universe) for connector in new_connectors: primitive.Add(connector, universe)
class ScanForConnections(ImmediateWithMemory): description = "Scan for connections" menu_info = MenuInfo("default/_Object:tools/_Builder:conscan", "_Scan for connections", order=(0, 4, 1, 6, 1, 0)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Connection scanner parameters", fields.group.Notebook([ ( "Geometry1", fields.group.Table(fields=[ ConnectionPointDescription( label_text="Connecting points", attribute_name="connect_description1", history_name="conscan_description", ), ConnectionPointDescription( label_text="Repulsive points", attribute_name="repulse_description1", history_name="conscan_description", ), ], cols=2), ), ( "Geometry2", fields.group.Table(fields=[ ConnectionPointDescription( label_text="Connecting points", attribute_name="connect_description2", history_name="conscan_description", ), ConnectionPointDescription( label_text="Repulsive points", attribute_name="repulse_description2", history_name="conscan_description", ), ], cols=2), ), ( "Parameters", fields.group.Table(fields=[ fields.faulty.Length( label_text="Action radius", attribute_name="action_radius", low=0.0, low_inclusive=False, ), fields.faulty.Length( label_text="Distance tolerance", attribute_name="hit_tolerance", low=0.0, low_inclusive=False, ), fields.group.Table(fields=[ fields.optional.RadioOptional( slave=fields.group.Table(fields=[ fields.edit.CheckButton( label_text="Allow inversion rotations", attribute_name="allow_inversions", ), fields.faulty.Length( label_text="Minimum triangle size", attribute_name="minimum_triangle_size", low=0.0, low_inclusive=False, ), ], label_text= "Allow free rotations")), fields.optional.RadioOptional( slave=fields.group.Table(fields=[ fields.composed.Rotation( label_text= "Relative rotation of the two structures", attribute_name="rotation2", ), ], label_text= "Use a fixed rotation")), ], cols=2), ]), ) ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) triangle_report_dialog = ConscanReportDialog([ ("calc_env", "Calculating environments"), ("comp_env", "Comparing environments"), ("calc_trans", "Calculating transformations"), ("mirror", "Adding inversions"), ("eval_con", "Evaluating connections"), ("elim_dup", "Eliminating duplicates"), ("send_con", "Receiving solutions"), ]) pair_report_dialog = ConscanReportDialog([ ("calc_env", "Calculating environments"), ("comp_env", "Comparing environments"), ("calc_trans", "Calculating transformations"), ("eval_con", "Evaluating connections"), ("elim_dup", "Eliminating duplicates"), ("send_con", "Receiving solutions"), ]) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.nodes) == 0 or len(cache.nodes) > 2: return False for Class in cache.classes: if not issubclass(Class, GLFrameBase): return False if len(cache.nodes) == 2 and not isinstance(cache.parent, GLContainerMixin): return False # C) passed all tests: return True @classmethod def default_parameters(cls): rotation2 = Rotation.from_properties(0.0, [1, 0, 0], False) result = Parameters() result.connect_description1 = (Expression("True"), Expression("node.get_radius()")) result.repulse_description1 = (Expression("True"), Expression("node.get_radius()")) result.connect_description2 = (Expression("True"), Expression("node.get_radius()")) result.repulse_description2 = (Expression("True"), Expression("node.get_radius()")) result.action_radius = 7 * angstrom result.distance_tolerance = 0.1 * angstrom result.hit_tolerance = 0.1 * angstrom result.allow_inversions = True result.minimum_triangle_size = 0.1 * angstrom result.rotation_tolerance = 0.05 result.rotation2 = Undefined(rotation2) return result def ask_parameters(self): if len(context.application.cache.nodes) == 1: if not isinstance(self.parameters.connect_description2, Undefined): self.parameters.connect_description2 = Undefined( self.parameters.connect_description2) if not isinstance(self.parameters.repulse_description2, Undefined): self.parameters.repulse_description2 = Undefined( self.parameters.repulse_description2) else: if isinstance(self.parameters.connect_description2, Undefined): self.parameters.connect_description2 = self.parameters.connect_description2.value if isinstance(self.parameters.repulse_description2, Undefined): self.parameters.repulse_description2 = self.parameters.repulse_description2.value node1, node2 = context.application.cache.nodes relative_rotation = node2.get_frame_relative_to(node1.parent) if isinstance(self.parameters.rotation2, Undefined): self.parameters.rotation2 = Undefined(relative_rotation) else: self.parameters.rotation2 = relative_rotation if self.parameters_dialog.run(self.parameters) != gtk.RESPONSE_OK: self.parameters.clear() return def do(self): cache = context.application.cache geometry_nodes = [[], []] def get_parameters(node, point_type, geometry_index, filter_expression, radius_expression): template = "An exception occured in the %%s expression\nfor the %s points of geometry %i." % ( point_type, geometry_index + 1) try: is_type = filter_expression(node) except Exception: raise UserError(template % "filter") if is_type: try: radius = radius_expression(node) except Exception: raise UserError(template % "radius") geometry_nodes[geometry_index].append(node) return True, radius else: return False, None def read_geometry(frame, geometry_index, connect_description, repulse_description): coordinates = [] connect_masks = [] radii = [] for child in frame.children: if isinstance(child, GLTransformationMixin) and \ isinstance(child.transformation, Translation): is_connect, radius = get_parameters( child, "connecting", geometry_index, *connect_description) if is_connect: coordinates.append(child.transformation.t) connect_masks.append(True) radii.append(radius) else: is_repulse, radius = get_parameters( child, "repulsive", geometry_index, *repulse_description) if is_repulse: coordinates.append(child.transformation.t) connect_masks.append(False) radii.append(radius) return Geometry( numpy.array(coordinates, float), numpy.array(connect_masks, bool), numpy.array(radii, float), ) inp = {} inp["geometry1"] = read_geometry(cache.nodes[0], 0, self.parameters.connect_description1, self.parameters.repulse_description1) if len(cache.nodes) == 2: inp["geometry2"] = read_geometry( cache.nodes[1], 1, self.parameters.connect_description2, self.parameters.repulse_description2) else: inp["geometry2"] = None inp["action_radius"] = self.parameters.action_radius inp["hit_tolerance"] = self.parameters.hit_tolerance if not isinstance(self.parameters.allow_inversions, Undefined): inp["allow_rotations"] = True inp["allow_inversions"] = self.parameters.allow_inversions inp["minimum_triangle_area"] = self.parameters.minimum_triangle_size**2 else: inp["allow_rotations"] = False if isinstance(self.parameters.rotation2, Undefined): inp["rotation2"] = Rotation.identity() else: inp["rotation2"] = self.parameters.rotation2 if inp["allow_rotations"]: connections = self.triangle_report_dialog.run(inp) else: connections = self.pair_report_dialog.run(inp) if connections is not None and len(connections) > 0: if len(cache.nodes) == 1: frame1 = cache.nodes[0] Duplicate = context.application.plugins.get_action("Duplicate") Duplicate() frame2 = cache.nodes[0] geometry_nodes[1] = geometry_nodes[0] else: frame1, frame2 = cache.nodes conscan_results = ConscanResults( targets=[frame1, frame2], connections=[( float(connection.quality), connection.transformation, [(geometry_nodes[0][first].get_index(), geometry_nodes[1][second].get_index()) for first, second in connection.pairs], [(geometry_nodes[0][second].get_index(), geometry_nodes[1][first].get_index()) for first, second in connection.pairs if connection.invertible], ) for connection in connections], ) primitive.Add(conscan_results, context.application.model.folder)
class CreateTube(ImmediateWithMemory): description = "Create Tube" menu_info = MenuInfo("default/_Object:tools/_Builder:generate", "_Create tube", order=(0, 4, 1, 6, 1, 0)) authors = [authors.toon_verstraelen] @staticmethod def analyze_selection(parameters=None): if not ImmediateWithMemory.analyze_selection(parameters): return False if context.application.model.universe.cell.active.sum() != 2: return False return True parameters_dialog = FieldsDialogSimple( "Tube parameters", fields.group.Table(fields=[ fields.faulty.Int( label_text="n", attribute_name="n", minimum=1, maximum=100, ), fields.faulty.Int( label_text="m", attribute_name="m", minimum=0, maximum=100, ), fields.edit.CheckButton( label_text="Flat", attribute_name="flat", ), fields.optional.RadioOptional( fields.group.Table(label_text="Periodic (along tube axis)", fields=[ fields.faulty.Length( label_text="Maximum tube length", attribute_name="max_length", low=0.0, low_inclusive=False, ), fields.faulty.Length( label_text="Maximum mismatch", attribute_name="max_error", low=0.0, low_inclusive=False, ), ])), fields.optional.RadioOptional( fields.group.Table(label_text="Not periodic", fields=[ fields.faulty.Length( label_text="Tube length", attribute_name="tube_length", low=0.0, low_inclusive=False, ), ])), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) @classmethod def default_parameters(cls): result = Parameters() result.n = 5 result.m = 1 result.flat = False result.max_length = 50 * angstrom result.max_error = 0.01 * angstrom result.tube_length = Undefined(50 * angstrom) return result def do(self): # the indices (n,m) that define the tube, see e.g. the wikipedia page # about nanotubes for the interpretation of these indices: # http://en.wikipedia.org/wiki/Carbon_nanotube n = self.parameters.n m = self.parameters.m periodic_tube = isinstance(self.parameters.tube_length, Undefined) universe = context.application.model.universe def define_flat(): "Reads and converts the unit cell vectors from the current model." # some parts of the algorithm have been arranged sub functions like # these, to reduce the number of local variables in self.do. This # should also clarify the code. active, inactive = universe.cell.active_inactive lengths, angles = universe.cell.parameters a = lengths[active[0]] b = lengths[active[1]] theta = angles[inactive[0]] return (numpy.array([a, 0], float), numpy.array([b * numpy.cos(theta), b * numpy.sin(theta)], float)) flat_a, flat_b = define_flat() def create_pattern(): "Read the atom positions and transform them to the flat coordinates" active, inactive = universe.cell.active_inactive a = universe.cell.matrix[:, active[0]] b = universe.cell.matrix[:, active[1]] c = numpy.cross(a, b) tmp_cell = UnitCell(numpy.array([a, b, c]).transpose()) rotation = tmp_cell.alignment_a return [(atom.number, rotation * atom.get_absolute_frame().t) for atom in iter_atoms([universe])] pattern = create_pattern() def define_big_periodic(): "Based on (n,m) calculate the size of the periodic sheet (that will be folded into a tube)." big_a = n * flat_a - m * flat_b norm_a = numpy.linalg.norm(big_a) radius = norm_a / (2 * numpy.pi) big_x = big_a / norm_a big_y = numpy.array([-big_x[1], big_x[0]], float) big_b = None stack_vector = flat_b - flat_a * numpy.dot( big_x, flat_b) / numpy.dot(big_x, flat_a) stack_length = numpy.linalg.norm(stack_vector) nominator = numpy.linalg.norm(stack_vector - flat_b) denominator = numpy.linalg.norm(flat_a) fraction = nominator / denominator stack_size = 1 while True: repeat = fraction * stack_size if stack_length * stack_size > self.parameters.max_length: break if abs(repeat - round(repeat) ) * denominator < self.parameters.max_error: big_b = stack_vector * stack_size break stack_size += 1 if big_b is None: raise UserError( "Could not create a periodic tube shorter than the given maximum length." ) rotation = numpy.array([big_x, big_y], float) return big_a, big_b, rotation, stack_vector, stack_size, radius def define_big_not_periodic(): "Based on (n,m) calculate the size of the non-periodic sheet (that will be folded into a tube)." big_a = n * flat_a - m * flat_b norm_a = numpy.linalg.norm(big_a) radius = norm_a / (2 * numpy.pi) big_x = big_a / norm_a big_y = numpy.array([-big_x[1], big_x[0]], float) stack_vector = flat_b - flat_a * numpy.dot( big_x, flat_b) / numpy.dot(big_x, flat_a) stack_length = numpy.linalg.norm(stack_vector) stack_size = int(self.parameters.tube_length / stack_length) big_b = stack_vector * stack_size rotation = numpy.array([big_x, big_y], float) return big_a, big_b, rotation, stack_vector, stack_size, radius if periodic_tube: big_a, big_b, rotation, stack_vector, stack_size, radius = define_big_periodic( ) else: big_a, big_b, rotation, stack_vector, stack_size, radius = define_big_not_periodic( ) def iter_translations(): "Yields the indices of the periodic images that are part of the tube." to_fractional = numpy.linalg.inv( numpy.array([big_a, big_b]).transpose()) col_len = int( numpy.linalg.norm(big_a + m * stack_vector) / numpy.linalg.norm(flat_a)) + 4 shift = numpy.dot(stack_vector - flat_b, flat_a) / numpy.linalg.norm(flat_a)**2 for row in xrange(-m - 1, stack_size + 1): col_start = int(numpy.floor(row * shift)) - 1 for col in xrange(col_start, col_start + col_len): p = col * flat_a + row * flat_b i = numpy.dot(to_fractional, p) if (i >= 0).all() and (i < 1 - 1e-15).all(): yield p #yield p, (i >= 0).all() and (i < 1).all() def iter_pattern(): for number, coordinate in pattern: yield number, coordinate.copy() # first delete everything the universe: while len(universe.children) > 0: primitive.Delete(universe.children[0]) # add the new atoms Atom = context.application.plugins.get_node("Atom") if self.parameters.flat: rot_a = numpy.dot(rotation, big_a) rot_b = numpy.dot(rotation, big_b) big_matrix = numpy.array([ [rot_a[0], rot_b[0], 0], [rot_a[1], rot_b[1], 0], [0, 0, 10 * angstrom], ], float) big_cell = UnitCell( big_matrix, numpy.array([True, periodic_tube, False], bool)) primitive.SetProperty(universe, "cell", big_cell) for p in iter_translations(): for number, coordinate in iter_pattern(): coordinate[:2] += p coordinate[:2] = numpy.dot(rotation, coordinate[:2]) translation = Translation(coordinate) primitive.Add( Atom(number=number, transformation=translation), universe) else: tube_length = numpy.linalg.norm(big_b) big_matrix = numpy.diag([radius * 2, radius * 2, tube_length]) big_cell = UnitCell( big_matrix, numpy.array([False, False, periodic_tube], bool)) primitive.SetProperty(universe, "cell", big_cell) for p in iter_translations(): for number, coordinate in iter_pattern(): coordinate[:2] += p coordinate[:2] = numpy.dot(rotation, coordinate[:2]) translation = Translation( numpy.array([ (radius + coordinate[2]) * numpy.cos(coordinate[0] / radius), (radius + coordinate[2]) * numpy.sin(coordinate[0] / radius), coordinate[1], ])) primitive.Add( Atom(number=number, transformation=translation), universe)
class DistributionDihedralAngles(ImmediateWithMemory): description = "Distribution of dihedral angles" menu_info = MenuInfo("default/_Object:tools/_Molecular:dist", "Distribution of _dihedral angles", order=(0, 4, 1, 5, 3, 2)) authors = [authors.toon_verstraelen] required_modules = ["pylab", "matplotlib"] parameters_dialog = FieldsDialogSimple( "Dihedral angle distribution parameters", fields.group.Table(fields=[ fields.faulty.Expression( label_text="Filter expression: atom 1", attribute_name="filter_atom1", history_name="filter_atom", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: bond 1-2", attribute_name="filter_bond12", history_name="filter_bond", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: atom 2", attribute_name="filter_atom2", history_name="filter_atom", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: bond 2-3", attribute_name="filter_bond23", history_name="filter_bond", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: atom 3", attribute_name="filter_atom3", history_name="filter_atom", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: bond 3-4", attribute_name="filter_bond34", history_name="filter_bond", width=250, height=60, ), fields.faulty.Expression( label_text="Filter expression: atom 4", attribute_name="filter_atom4", history_name="filter_atom", width=250, height=60, ), ], cols=2), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.nodes) == 0: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.filter_atom1 = Expression() result.filter_bond12 = Expression() result.filter_atom2 = Expression() result.filter_bond23 = Expression() result.filter_atom3 = Expression() result.filter_bond34 = Expression() result.filter_atom4 = Expression() return result def do(self): for key, val in self.parameters.__dict__.iteritems(): if isinstance(val, Expression): val.compile_as("<%s>" % key) val.variables = (key[7:11], ) parent = context.application.cache.common_parent if parent is None: parent = context.application.model.universe angles = [] graph = create_molecular_graph(context.application.cache.nodes) match_definition = DihedralAnglePattern(criteria_sets=[ CriteriaSet( thing_criteria={ 0: IndexToAtoms(self.parameters.filter_atom1), 1: IndexToAtoms(self.parameters.filter_atom2), 2: IndexToAtoms(self.parameters.filter_atom3), 3: IndexToAtoms(self.parameters.filter_atom4), }, relation_criteria={ 0: IndexToBonds(self.parameters.filter_bond12), 1: IndexToBonds(self.parameters.filter_bond23), 2: IndexToBonds(self.parameters.filter_bond34), }, ) ], ) try: atoms = graph.molecule.atoms match_generator = GraphSearch(match_definition) for match in match_generator(graph): point1 = atoms[match.forward[0]].get_absolute_frame().t point2 = atoms[match.forward[1]].get_absolute_frame().t point3 = atoms[match.forward[2]].get_absolute_frame().t point4 = atoms[match.forward[3]].get_absolute_frame().t normal1 = numpy.cross(parent.shortest_vector(point2 - point1), parent.shortest_vector(point2 - point3)) normal2 = numpy.cross(parent.shortest_vector(point3 - point4), parent.shortest_vector(point3 - point2)) if numpy.linalg.norm(normal1) > 1e-8 and \ numpy.linalg.norm(normal2) > 1e-8: angles.append(angle(normal1, normal2)) except: raise UserError( "An error occured while sampling the dihedral angles.", "If this is an error in one of the filter expressions,\n" + "one should see the expression mentioned below as <filter_...>.\n\n" ) comments = [ "atom 1 filter expression: %s" % self.parameters.filter_atom1.code, "bond 1-2 filter expression: %s" % self.parameters.filter_bond12.code, "atom 2 filter expression: %s" % self.parameters.filter_atom2.code, "bond 2-3 filter expression: %s" % self.parameters.filter_bond23.code, "atom 3 filter expression: %s" % self.parameters.filter_atom3.code, "bond 3-4 filter expression: %s" % self.parameters.filter_bond34.code, "atom 4 filter expression: %s" % self.parameters.filter_atom4.code, ] if len(angles) > 0: distribution_dialog = DistributionDialog() distribution_dialog.run(numpy.array(angles), "Angle", "Dihedral angle", comments) else: raise UserError("No dihedral angles match the given criteria.")
class SketchOptions(GladeWrapper): edit_erase_filter = FieldsDialogSimple( "Edit the Erase filter", fields.faulty.Expression( label_text="Erase filter expression", attribute_name="erase_filter", show_popup=True, history_name="filter", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) def __init__(self): GladeWrapper.__init__(self, "plugins/molecular/gui.glade", "wi_sketch", "window") self.window.hide() self.init_callbacks(self.__class__) self.init_proxies([ "cb_object", "cb_vector", "cb_erase_filter", "bu_edit_erase_filter", "la_current", "bu_set_atom", "cb_bondtype", "hbox_atoms", "hbox_quickpicks", "hbox_fragments", "la_fragment", "cb_fragment" ]) self.erase_filter = Expression("True") #Initialize atom number - this can be changed anytime with the edit_atom_number dialog self.atom_number = 6; # Initialize the GUI # 1) common parts of the comboboxes def render_icon(column, cell, model, iter): cell.set_property( "pixbuf", context.application.plugins.get_node(model.get_value(iter, 0)).icon ) # 2) fill the objects combo box self.object_store = gtk.ListStore(str) self.object_store.append(["Atom"]) self.object_store.append(["Fragment"]) self.object_store.append(["Point"]) self.object_store.append(["Sphere"]) self.object_store.append(["Box"]) self.cb_object.set_model(self.object_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_object.pack_start(renderer_pixbuf, expand=False) self.cb_object.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_object.pack_start(renderer_text, expand=True) self.cb_object.add_attribute(renderer_text, "text", 0) self.cb_object.set_active(0) # 3) fill the vector combo box self.vector_store = gtk.ListStore(str) self.vector_store.append(["Bond"]) self.vector_store.append(["Arrow"]) self.vector_store.append(["Spring"]) self.cb_vector.set_model(self.vector_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_vector.pack_start(renderer_pixbuf, expand=False) self.cb_vector.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_vector.pack_start(renderer_text, expand=True) self.cb_vector.add_attribute(renderer_text, "text", 0) self.cb_vector.set_active(0) # 4) fill the bond type combo box self.bondtype_store = gtk.ListStore(str,int) self.bondtype_store.append(["Single bond",BOND_SINGLE]) self.bondtype_store.append(["Double bond",BOND_DOUBLE]) self.bondtype_store.append(["Triple bond",BOND_TRIPLE]) self.bondtype_store.append(["Hybrid bond",BOND_HYBRID]) self.bondtype_store.append(["Hydrogen bond",BOND_HYDROGEN]) self.cb_bondtype.set_model(self.bondtype_store) #no icons like the others, just text here renderer_text = gtk.CellRendererText() self.cb_bondtype.pack_start(renderer_text, expand=True) self.cb_bondtype.add_attribute(renderer_text, "text", 0) self.cb_bondtype.set_active(0) # register quick pick config setting config = context.application.configuration config.register_setting( "sketch_quickpicks", [6,7,8,9,10,11], ) #WOUTER I messed up this setting and now Zeobuilder keeps crashing cause it was set to empty config.sketch_quickpicks = [6,7,8,9,10,11] # 5)create the "quick pick" atom buttons for index in xrange(len(config.sketch_quickpicks)): atomnumber = config.sketch_quickpicks[index] bu_element = gtk.Button("") bu_element.set_label("%s" % periodic[atomnumber].symbol) bu_element.connect("clicked", self.on_bu_element_clicked, index) # add to hbox self.hbox_quickpicks.pack_start(bu_element) bu_element.show() # 6)fill the fragment combo box with filenames from share/fragments fragmentdir = context.get_share_filename('fragments') self.fragment_store = gtk.ListStore(str) for filename in os.listdir (fragmentdir): # Ignore subfolders and files with extension other than cml if os.path.isdir (os.path.join (fragmentdir, filename)) or filename[-3:] != 'cml': continue self.fragment_store.append([filename[:-4]]) self.cb_fragment.set_model(self.fragment_store) renderer_text = gtk.CellRendererText() self.cb_fragment.pack_start(renderer_text, expand=True) self.cb_fragment.add_attribute(renderer_text, "text", 0) self.cb_fragment.set_active(0) self.doing_fragment = False #at init we do atoms :) def on_window_delete_event(self, window, event): return True def on_bu_edit_erase_filter_clicked(self, button): self.edit_erase_filter.run(self) def on_bu_set_atom_clicked(self, button): self.edit_atom_number = FieldsDialogSimple( "Select atom number", fields.edit.Element(attribute_name="atom_number"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) self.edit_atom_number.run(self) atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s " % str(atom_symbol)) def on_cb_object_changed(self, combo): # When the selected object is an atom, show the extra button. self.hbox_atoms.hide() self.la_current.hide() self.hbox_fragments.hide() self.doing_fragment = False if(self.object_store.get_value(self.cb_object.get_active_iter(),0)=="Atom"): self.hbox_atoms.show() self.la_current.show() if(self.object_store.get_value(self.cb_object.get_active_iter(),0)=="Fragment"): self.doing_fragment = True self.cb_fragment.show() #self.la_fragment.show() self.hbox_fragments.show() def on_bu_element_clicked(self, widget, index): self.atom_number = context.application.configuration.sketch_quickpicks[index] atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s" % str(atom_symbol)) def on_cb_vector_changed(self, combo): # When the selected object is an atom, show the extra button. if (self.vector_store.get_value(self.cb_vector.get_active_iter(),0)=="Bond"): self.cb_bondtype.show() else: self.cb_bondtype.hide() def get_new(self,position): object_type = self.object_store.get_value(self.cb_object.get_active_iter(), 0) print object_type if object_type == "Fragment": #if it's a 'Fragment', set fragment name to current item in combo box fragment = context.application.plugins.get_node("Fragment")(self.fragment_store.get_value(self.cb_fragment.get_active_iter(),0)) fragment.set_name(self.fragment_store.get_value(self.cb_fragment.get_active_iter(),0)) new = fragment.get_in_frame(position) else: new = context.application.plugins.get_node(object_type)() if object_type == "Atom": #if it's an 'Atom', set the atom number to the current atom number? new.set_number(self.atom_number) new.set_name(periodic[self.atom_number].symbol) new.transformation.t[:] = position return new def add_new(self, position, parent): print "position:" print position new = self.get_new(position) print new primitive.Add(new, parent) def replace(self, gl_object): print gl_object if not gl_object.get_fixed(): if self.doing_fragment: print "replacing atom with fragment" position = gl_object.transformation.t parent = gl_object.parent new = self.get_new(position) new.addToModel(position,parent) primitive.Delete(gl_object) else: new = self.get_new(gl_object.transformation.t) # old way: new.transformation.t[:] = for reference in gl_object.references[::-1]: if not reference.check_target(new): return parent = gl_object.parent primitive.Add(new, parent) for reference in gl_object.references[::-1]: reference.set_target(new) primitive.Delete(gl_object) def connect(self, gl_object1, gl_object2): print "connecting:" print gl_object1,gl_object2 try: new = context.application.plugins.get_node( self.vector_store.get_value(self.cb_vector.get_active_iter(), 0) )(targets=[gl_object1, gl_object2]) except TargetError: return if(self.vector_store.get_value(self.cb_vector.get_active_iter(), 0)=="Bond"): new.set_bond_type(self.bondtype_store.get_value(self.cb_bondtype.get_active_iter(),1)) primitive.Add(new, common_parent([gl_object1.parent, gl_object2.parent])) def erase_at(self, p, parent): for node in context.application.main.drawing_area.yield_hits((p[0]-2, p[1]-2, p[0]+2, p[1]+2)): try: match = ( node is not None and node != parent and node.is_indirect_child_of(parent) and node.model == context.application.model and ( not self.cb_erase_filter.get_active() or self.erase_filter(node) ) ) except Exception: raise UserError("An exception occured while evaluating the erase filter expression.") if match: primitive.Delete(node) def tool_draw(self, p1, p2): drawing_area = context.application.main.drawing_area r1 = drawing_area.screen_to_camera(p1) r2 = drawing_area.screen_to_camera(p2) context.application.vis_backend.tool("line", r1, r2) def tool_special(self, p1, p2): pass def move_special(self, gl_object1, gl_object2, p1, p2): pass def click_special(self, gl_object): pass
class OptimizeSprings(ImmediateWithMemory): description = "Optimize springs" menu_info = MenuInfo("default/_Object:tools/_Builder:spring", "_Optimize springs", order=(0, 4, 1, 6, 0, 1)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Minimization parameters", fields.group.Table(fields=[ fields.edit.CheckButton( label_text="Allow free rotation", attribute_name="allow_rotation", ), fields.faulty.Float( label_text="Update interval [s]", attribute_name="update_interval", low=0.0, low_inclusive=True, ), fields.faulty.Int(label_text="Number of iterations between update", attribute_name="update_steps", minimum=1), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)), ) report_dialog = OptimizationReportDialog() @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache for Class in cache.classes: if not issubclass(Class, Spring): return False if cache.parent is None: return False spring_problem = cache.spring_problem if spring_problem is None: return False if len(spring_problem.frames) == 0: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.allow_rotation = True result.update_interval = 0.4 result.update_steps = 1 return result def do(self): cache = context.application.cache parent = cache.parent involved_frames = cache.spring_problem.frames springs = cache.spring_problem.springs max_step = [] old_transformations = [(frame, frame.transformation) for frame in involved_frames if frame is not None] variable_indices = dict((frame, index) for index, frame in enumerate( frame for frame in involved_frames if frame is not None)) cost_function = iterative.expr.Root(1, 10, True) for frame in involved_frames: if frame is None: pass elif self.parameters.allow_rotation and isinstance( frame.transformation, Complete): variable = iterative.var.Frame( frame.transformation.r, frame.transformation.t, ) cost_function.register_state_variable(variable) constraint = iterative.expr.Orthonormality(1e-10) constraint.register_input_variable(variable) max_step.extend([ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 1.0, 1.0, 1.0 ]) elif isinstance(frame.transformation, Translation): variable = iterative.var.Translation( frame.transformation.r, frame.transformation.t, ) cost_function.register_state_variable(variable) max_step.extend([1.0, 1.0, 1.0]) else: raise UserError( "The involved frames shoud be at least capable of being translated." ) for spring, frames in springs.iteritems(): spring_term = iterative.expr.Spring(spring.rest_length) for target, frame in frames.iteritems(): if frame is None: spring_term.register_input_variable( iterative.expressions.NoFrame(), target.get_frame_up_to(parent).t) else: spring_term.register_input_variable( cost_function.state_variables[variable_indices[frame]], target.get_frame_up_to(frame).t) max_step = numpy.array(max_step, float) minimize = iterative.alg.DefaultMinimize( cost_function, max_step, max_step * 1e-8, ) result = self.report_dialog.run( minimize, involved_frames, self.parameters.update_interval, self.parameters.update_steps, len(springs), ) if result != gtk.RESPONSE_OK: for frame, transformation in old_transformations: frame.transformation = transformation frame.invalidate_transformation_list() raise CancelException for frame, transformation in old_transformations: primitive.SetProperty( frame, "transformation", transformation, done=True, )
class SelectChildrenByExpression(ImmediateWithMemory): description = "Select children by expression" menu_info = MenuInfo("default/_Select:default", "Children by _expression", order=(0, 3, 0, 4)) authors = [authors.toon_verstraelen] SELECT_PLAIN = 0 SELECT_RECURSIVE = 1 SELECT_RECURSIVE_IF_MATCH = 2 parameters_dialog = FieldsDialogSimple( "Select children by expression", fields.group.Table( fields=[ fields.edit.ComboBox( choices=[ (SELECT_PLAIN, "No"), (SELECT_RECURSIVE, "Yes"), (SELECT_RECURSIVE_IF_MATCH, "Yes if match") ], label_text="Recursive", attribute_name="recursive", show_popup=False ), fields.faulty.Expression( label_text="Filter expression:", attribute_name="expression", show_popup=True, history_name="filter", ) ], label_text="Selection rules" ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating cache = context.application.cache if len(cache.containers_with_children) + len(cache.referents_with_children) == 0: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.expression = Expression() result.recursive = cls.SELECT_PLAIN return result def do(self): cache = context.application.cache parents = cache.nodes containers = cache.containers_with_children main = context.application.main def toggle_children(parent): for node in parent.children: match = self.parameters.expression(node) if match: main.toggle_selection(node, on=True) if ((self.parameters.recursive == self.SELECT_RECURSIVE_IF_MATCH) and match) or (self.parameters.recursive == self.SELECT_RECURSIVE): if isinstance(node, ParentMixin): toggle_children(node) for parent in parents: main.toggle_selection(parent, on=False) try: for container in containers: toggle_children(container) except Exception: main.tree_selection.unselect_all() for parent in parents: main.toggle_selection(parent, on=True) raise UserError("An exception occured while evaluating the filter expression.")
class RotateAboutAxisDialog(ImmediateWithMemory): description = "Apply rotation" menu_info = MenuInfo("default/_Object:tools/_Transform:dialogs", "Rotate objects about axis", order=(0, 4, 1, 2, 1, 1)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Rotation about axis", fields.group.Table( [ fields.composed.Translation( label_text="", attribute_name="center", vector_name="c.%s", ), fields.composed.Rotation( label_text="", attribute_name="rotation", ) ], label_text="The rotation axis is defined by center c and axis n."), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False cache = context.application.cache if len(cache.nodes) == 1: if not isinstance(cache.last, GLTransformationMixin) or \ not isinstance(cache.last.transformation, Rotation): return False if cache.last.get_fixed(): return False elif len(cache.nodes) == 2: if not ((isinstance(cache.last, GLTransformationMixin) and isinstance(cache.last.transformation, Translation)) or isinstance(cache.last, Vector)): return False if not isinstance(cache.next_to_last, GLTransformationMixin) or \ not isinstance(cache.next_to_last.transformation, Complete): return False if cache.last.get_fixed(): return False else: return False if cache.some_nodes_fixed: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() result.center = Translation.identity() result.rotation = Rotation.identity() return result def ask_parameters(self): cache = context.application.cache nodes = cache.nodes last = cache.last next_to_last = cache.next_to_last if isinstance(last, Vector): if (len(nodes) >= 2) and isinstance(next_to_last, Vector): parent = nodes[-2].parent b1 = last.children[0].translation_relative_to(parent) e1 = last.children[1].translation_relative_to(parent) b2 = next_to_last.children[0].translation_relative_to(parent) e2 = next_to_last.children[1].translation_relative_to(parent) if (b1 is not None) and (e1 is not None) and ( b2 is not None) and (e2 is not None): angle = compute_angle(e1 - b1, e2 - b2) axis = numpy.cross(e1 - b1, e2 - b2) self.parameters.center = Translation(0.5 * (b1 + b2)) self.parameters.rotation = Rotation.from_properties( angle, axis, False) else: parent = next_to_last.parent b = last.children[0].translation_relative_to(parent) e = last.children[1].translation_relative_to(parent) if (b is not None) and (e is not None): self.parameters.center = Translation(b) self.parameters.rotation = Rotation.from_properties( numpy.pi * 0.25, e - b, False) elif isinstance(last, GLTransformationMixin) and isinstance( last.transformation, Translation): parent = last.parent self.parameters.center = Translation( last.get_frame_relative_to(parent).t) self.parameters.rotation = Rotation.identity() else: self.parameters.center = Translation( calculate_center(cache.translations)) if self.parameters_dialog.run(self.parameters) != gtk.RESPONSE_OK: self.parameters.clear() def do(self): transformation = self.parameters.center * self.parameters.rotation * self.parameters.center.inv for victim in context.application.cache.transformed_nodes: primitive.Transform(victim, transformation)
class SketchOptions(GladeWrapper): edit_erase_filter = FieldsDialogSimple( "Edit the Erase filter", fields.faulty.Expression( label_text="Erase filter expression", attribute_name="erase_filter", show_popup=True, history_name="filter", ), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) def __init__(self): GladeWrapper.__init__(self, "plugins/molecular/gui.glade", "wi_sketch", "window") self.window.hide() self.init_callbacks(self.__class__) self.init_proxies([ "cb_object", "cb_vector", "cb_erase_filter", "bu_edit_erase_filter", "la_current", "bu_set_atom", "cb_bondtype", "hbox_atoms", "hbox_quickpicks", "hbox_fragments", "la_fragment", "cb_fragment" ]) self.erase_filter = Expression("True") #Initialize atom number - this can be changed anytime with the edit_atom_number dialog self.atom_number = 6; # Initialize the GUI # 1) common parts of the comboboxes def render_icon(column, cell, model, iter): if model.get_value(iter, 0) == "Fragment": cell.set_property( "pixbuf", context.application.plugins.get_node("Atom").icon ) else: cell.set_property( "pixbuf", context.application.plugins.get_node(model.get_value(iter, 0)).icon ) # 2) fill the objects combo box self.object_store = gtk.ListStore(str) self.object_store.append(["Atom"]) self.object_store.append(["Fragment"]) self.object_store.append(["Point"]) self.object_store.append(["Sphere"]) self.object_store.append(["Box"]) self.cb_object.set_model(self.object_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_object.pack_start(renderer_pixbuf, expand=False) self.cb_object.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_object.pack_start(renderer_text, expand=True) self.cb_object.add_attribute(renderer_text, "text", 0) self.cb_object.set_active(0) # 3) fill the vector combo box self.vector_store = gtk.ListStore(str) self.vector_store.append(["Bond"]) self.vector_store.append(["Arrow"]) self.vector_store.append(["Spring"]) self.cb_vector.set_model(self.vector_store) renderer_pixbuf = gtk.CellRendererPixbuf() self.cb_vector.pack_start(renderer_pixbuf, expand=False) self.cb_vector.set_cell_data_func(renderer_pixbuf, render_icon) renderer_text = gtk.CellRendererText() self.cb_vector.pack_start(renderer_text, expand=True) self.cb_vector.add_attribute(renderer_text, "text", 0) self.cb_vector.set_active(0) # 4) fill the bond type combo box self.bondtype_store = gtk.ListStore(str,int) self.bondtype_store.append(["Single bond",BOND_SINGLE]) self.bondtype_store.append(["Double bond",BOND_DOUBLE]) self.bondtype_store.append(["Triple bond",BOND_TRIPLE]) self.bondtype_store.append(["Hybrid bond",BOND_HYBRID]) self.bondtype_store.append(["Hydrogen bond",BOND_HYDROGEN]) self.cb_bondtype.set_model(self.bondtype_store) #no icons like the others, just text here renderer_text = gtk.CellRendererText() self.cb_bondtype.pack_start(renderer_text, expand=True) self.cb_bondtype.add_attribute(renderer_text, "text", 0) self.cb_bondtype.set_active(0) # register quick pick config setting config = context.application.configuration config.register_setting( "sketch_quickpicks", [6,7,8,9,10,11], DialogFieldInfo("Sketch tool", (0,2), fields.faulty.IntegerList( label_text="Quick pick atoms (applies after restart)", attribute_name="sketch_quickpicks", )), ) # 5)create the "quick pick" atom buttons for index in xrange(len(config.sketch_quickpicks)): atomnumber = config.sketch_quickpicks[index] bu_element = gtk.Button("") bu_element.set_label("%s" % periodic[atomnumber].symbol) bu_element.connect("clicked", self.on_bu_element_clicked, index) # add to hbox self.hbox_quickpicks.pack_start(bu_element) bu_element.show() # 6)fill the fragment combo box with filenames from share/fragments fragment_dir = context.get_share_filename('fragments') self.fragment_store = gtk.ListStore(str) for filename in sorted(os.listdir(fragment_dir)): # Ignore subfolders and files with extension other than cml if os.path.isdir (os.path.join (fragment_dir, filename)) or filename[-3:] != 'cml': continue self.fragment_store.append([filename[:-4]]) self.cb_fragment.set_model(self.fragment_store) renderer_text = gtk.CellRendererText() self.cb_fragment.pack_start(renderer_text, expand=True) self.cb_fragment.add_attribute(renderer_text, "text", 0) self.cb_fragment.set_active(0) @property def current_object(self): return self.object_store.get_value(self.cb_object.get_active_iter(),0) @property def current_fragment(self): return self.fragment_store.get_value(self.cb_fragment.get_active_iter(), 0) def on_window_delete_event(self, window, event): self.window.hide() return True def on_bu_edit_erase_filter_clicked(self, button): self.edit_erase_filter.run(self) def on_bu_set_atom_clicked(self, button): self.edit_atom_number = FieldsDialogSimple( "Select atom number", fields.edit.Element(attribute_name="atom_number"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) self.edit_atom_number.run(self) atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s " % str(atom_symbol)) def on_cb_object_changed(self, combo): self.hbox_atoms.hide() self.la_current.hide() self.hbox_fragments.hide() if(self.current_object=="Atom"): self.hbox_atoms.show() self.la_current.show() if(self.current_object=="Fragment"): self.cb_fragment.show() self.hbox_fragments.show() def on_bu_element_clicked(self, widget, index): self.atom_number = context.application.configuration.sketch_quickpicks[index] atom_symbol = periodic[self.atom_number].symbol self.la_current.set_label("Current: %s" % str(atom_symbol)) def on_cb_vector_changed(self, combo): # When the selected object is an atom, show the extra button. if (self.vector_store.get_value(self.cb_vector.get_active_iter(),0)=="Bond"): self.cb_bondtype.show() else: self.cb_bondtype.hide() def get_new(self, position): if self.current_object == "Fragment": filename = context.get_share_filename('fragments/%s.cml' % self.current_fragment) molecule = load_cml(filename)[0] Frame = context.application.plugins.get_node("Frame") new = Frame(name=self.current_fragment) load_filter = context.application.plugins.get_load_filter('cml') load_filter.load_molecule(new, molecule) else: NewClass = context.application.plugins.get_node(self.current_object) new = NewClass() if self.current_object == "Atom": new.set_number(self.atom_number) new.set_name(periodic[self.atom_number].symbol) translation = Translation(position) primitive.Transform(new, translation) return new def add_new(self, position, parent): new = self.get_new(position) if self.current_object == "Fragment": result = new.children[1] primitive.Add(new, parent) UnframeAbsolute = context.application.plugins.get_action("UnframeAbsolute") UnframeAbsolute([new]) #return result else: primitive.Add(new, parent) return new def replace(self, gl_object): if not gl_object.get_fixed(): new = self.get_new(gl_object.transformation.t) # select a target object, i.e. the one the will be connected with # the bonds/vectors/... of the replaced object if(self.current_object == "Fragment"): # fragments are inserted as frames # take atom with index 1 as target target_object = new.children[1] else: target_object = new # check if all connections to the replaced object are applicable # to the new object. if not, then do not perform the replacement # and return early. for reference in gl_object.references[::-1]: if not reference.check_target(target_object): return # add the new object parent = gl_object.parent primitive.Add(new, parent) if(self.current_object == "Fragment"): # Fix the rotation and translation of the molecular fragment. # Rotation Bond = context.application.plugins.get_node("Bond") if len(gl_object.references) == 1 and isinstance(gl_object.references[0].parent, Bond): bond1 = gl_object.references[0].parent direction1 = bond1.shortest_vector_relative_to(parent) if bond1.children[0].target != gl_object: direction1 *= -1 bond2 = new.children[0].references[0].parent direction2 = bond2.shortest_vector_relative_to(parent) if bond2.children[0].target != target_object: direction2 *= -1 axis = numpy.cross(direction2, direction1) if numpy.linalg.norm(axis) < 1e-8: axis = random_orthonormal(direction1) angle = compute_angle(direction1, direction2) rotation = Rotation.from_properties(angle, axis, False) primitive.Transform(new, rotation) else: bond1 = None # Tranlsation pos_old = new.children[1].get_frame_relative_to(parent).t pos_new = gl_object.transformation.t translation = Translation(pos_new - pos_old) primitive.Transform(new, translation) if bond1 != None: # bond length old_length = numpy.linalg.norm(direction1) new_length = bonds.get_length(new.children[1].number, bond1.get_neighbor(gl_object).number) translation = Translation(-direction1/old_length*(new_length-old_length)) primitive.Transform(new, translation) # let the references to the replaced object point to the new object for reference in gl_object.references[::-1]: try: primitive.SetTarget(reference, target_object) except primitive.PrimitiveError: primitive.Delete(reference.parent) # delete the replaced object primitive.Delete(gl_object) if(self.current_object == "Fragment"): # Delete the first atom in the fragment primitive.Delete(new.children[0]) # Unframe the fragment UnframeAbsolute = context.application.plugins.get_action("UnframeAbsolute") UnframeAbsolute([new]) def connect(self, gl_object1, gl_object2): try: new = context.application.plugins.get_node( self.vector_store.get_value(self.cb_vector.get_active_iter(), 0) )(targets=[gl_object1, gl_object2]) except TargetError: return if(self.vector_store.get_value(self.cb_vector.get_active_iter(), 0)=="Bond"): new.set_bond_type(self.bondtype_store.get_value(self.cb_bondtype.get_active_iter(),1)) primitive.Add(new, common_parent([gl_object1.parent, gl_object2.parent])) def erase_at(self, p, parent): for node in context.application.main.drawing_area.iter_hits((p[0]-2, p[1]-2, p[0]+2, p[1]+2)): try: match = ( node is not None and node != parent and node.is_indirect_child_of(parent) and node.model == context.application.model and ( not self.cb_erase_filter.get_active() or self.erase_filter(node) ) ) except Exception: raise UserError("An exception occured while evaluating the erase filter expression.") if match: primitive.Delete(node) def tool_draw(self, p1, p2): drawing_area = context.application.main.drawing_area r1 = drawing_area.screen_to_camera(p1) r2 = drawing_area.screen_to_camera(p2) context.application.vis_backend.tool("line", r1, r2) def tool_special(self, p1, p2): pass def move_special(self, gl_object1, gl_object2, p1, p2): pass def click_special(self, gl_object): pass
class AutoConnectParameters(AutoConnectMixin, ImmediateWithMemory): description = "Add bonds (parameters)" menu_info = MenuInfo("default/_Object:tools/_Molecular:add", "_Add bonds (parameters)", order=(0, 4, 1, 5, 1, 1)) authors = [authors.toon_verstraelen] parameters_dialog = FieldsDialogSimple( "Bond specification", fields.group.Table([ fields.faulty.Int( label_text="Atom number 1", attribute_name="number1", show_popup=False, minimum=1, maximum=118 ), fields.faulty.Int( label_text="Atom number 2", attribute_name="number2", show_popup=False, minimum=1, maximum=118 ), fields.faulty.Length( label_text="Maximum bond length", attribute_name="distance", show_popup=False, low=0.0, low_inclusive=False ), fields.edit.ComboBox( choices=[ (BOND_SINGLE, "Single bond"), (BOND_DOUBLE, "Double bond"), (BOND_TRIPLE, "Triple bond")], label_text="Bond type", attribute_name="bond_type" ), ]), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)) ) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False if not AutoConnectMixin.analyze_selection(): return False # C) passed all tests: return True def allow_node(self, node): return ( isinstance(node, context.application.plugins.get_node("Atom")) and ( node.number == self.parameters.number1 or node.number == self.parameters.number2 ) ) def get_vector(self, atom1, atom2, distance): if (((atom1.number == self.parameters.number1) and (atom2.number == self.parameters.number2)) or ((atom1.number == self.parameters.number2) and (atom2.number == self.parameters.number1))) and \ atom1 not in atom2.iter_neighbors(): if self.parameters.distance >= distance: return Bond(bond_type=self.parameters.bond_type, targets=[atom1, atom2]) return None @classmethod def default_parameters(cls): result = Parameters() result.number1 = 6 result.number2 = 6 result.distance = 0.5 result.bond_type = BOND_SINGLE return result def do(self): AutoConnectMixin.do(self, max([1, self.parameters.distance]))
class UnitCellToCluster(ImmediateWithMemory): description = "Convert the unit cell to a cluster" menu_info = MenuInfo("default/_Object:tools/_Unit Cell:default", "_To cluster", order=(0, 4, 1, 4, 0, 0)) authors = [authors.toon_verstraelen] store_last_parameters = False parameters_dialog = FieldsDialogSimple( "Unit cell to cluster", fields.group.Table( fields=[ fields.optional.CheckOptional( fields.composed.ComposedArray( FieldClass=fields.faulty.Float, array_name=(ridge + ".%s"), suffices=["min", "max"], attribute_name="interval_%s" % ridge.lower(), one_row=True, short=False, )) for ridge in ["a", "b", "c"] ], label_text="The retained region in fractional coordinates:"), ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK))) @staticmethod def analyze_selection(parameters=None): # A) calling ancestor if not ImmediateWithMemory.analyze_selection(parameters): return False # B) validating universe = context.application.model.universe if sum(universe.cell.active) == 0: return False if hasattr(parameters, "interval_a") and not universe.cell.active[0]: return False if hasattr(parameters, "interval_b") and not universe.cell.active[1]: return False if hasattr(parameters, "interval_c") and not universe.cell.active[2]: return False # C) passed all tests: return True @classmethod def default_parameters(cls): result = Parameters() universe = context.application.model.universe if universe.cell.active[0]: result.interval_a = numpy.array([0.0, universe.repetitions[0]], float) if universe.cell.active[1]: result.interval_b = numpy.array([0.0, universe.repetitions[1]], float) if universe.cell.active[2]: result.interval_c = numpy.array([0.0, universe.repetitions[2]], float) return result def do(self): universe = context.application.model.universe def vector_acceptable(vector, cell_vector): return abs( numpy.dot(vector, cell_vector) / numpy.dot(cell_vector, cell_vector)) < 0.5 def extend_to_cluster(axis, interval): if (interval is None) or isinstance(interval, Undefined): return assert universe.cell.active[axis] interval.sort() index_min = int(numpy.floor(interval[0])) index_max = int(numpy.ceil(interval[1])) old_points = [ node for node in universe.children if (isinstance(node, GLTransformationMixin) and isinstance(node.transformation, Translation)) ] if len(old_points) == 0: return old_connections = [ node for node in universe.children if (isinstance(node, ReferentMixin) and reduce( (lambda x, y: x and y), (isinstance(child, SpatialReference) for child in node.children), True, )) ] # replication of the points new_points = {} for old_point in old_points: # determine the wrapped position old_pos = old_point.transformation.t.copy() old_frac = universe.cell.to_fractional(old_pos) old_index = numpy.floor(old_frac).astype(int) old_pos -= universe.cell.to_cartesian(old_index) old_frac -= old_index del old_index # make copies for cell_index in xrange(index_min, index_max): position = old_pos + universe.cell.matrix[:, axis] * cell_index if (old_frac[axis] + cell_index < interval[0]) or ( old_frac[axis] + cell_index > interval[1]): continue state = old_point.__getstate__() state["transformation"] = state[ "transformation"].copy_with(t=position) new_point = old_point.__class__(**state) new_points[(old_point, cell_index)] = new_point new_connections = [] # replication of the connections for cell_index in xrange(index_min - 1, index_max + 1): for connection in old_connections: old_target0 = connection.children[0].target new_target0 = new_points.get((old_target0, cell_index)) if new_target0 is None: continue new_targets = [new_target0] for reference in connection.children[1:]: abort = True old_target1 = reference.target for offset in 0, 1, -1: new_target1 = new_points.get( (old_target1, cell_index + offset)) if new_target1 is not None: delta = new_target0.transformation.t - new_target1.transformation.t if vector_acceptable( delta, universe.cell.matrix[:, axis]): new_targets.append(new_target1) abort = False break if abort: break if abort: del new_targets continue state = connection.__getstate__() state["targets"] = new_targets new_connections.append(connection.__class__(**state)) # remove the existing points and connections for node in old_connections: primitive.Delete(node) del old_connections for node in old_points: primitive.Delete(node) del old_points # remove the periodicity new_active = universe.cell.active.copy() new_active[axis] = False new_cell = universe.cell.copy_with(active=new_active) primitive.SetProperty(universe, "cell", new_cell) # add the new nodes for node in new_points.itervalues(): primitive.Add(node, universe) for connection in new_connections: primitive.Add(connection, universe) if hasattr(self.parameters, "interval_a"): extend_to_cluster(0, self.parameters.interval_a) if hasattr(self.parameters, "interval_b"): extend_to_cluster(1, self.parameters.interval_b) if hasattr(self.parameters, "interval_c"): extend_to_cluster(2, self.parameters.interval_c)