1from __future__ import division 2 3import os 4import sys 5import pymol 6 7from pymol.Qt import QtGui, QtCore, QtWidgets 8from pymol.Qt.utils import getSaveFileNameWithExt, AsyncFunc 9from pymol.Qt.utils import PopupOnException 10 11if sys.version_info[0] < 3: 12 import urllib 13else: 14 import urllib.request as urllib 15 16 17def load_dialog(parent, fname, **kwargs): 18 ''' 19 Load a file into PyMOL. May show a file format specific options 20 dialog (e.g. trajectory loading dialog). Registers the filename 21 in the "recent files" history. 22 ''' 23 if '://' not in fname: 24 parent.initialdir = os.path.dirname(fname) 25 26 parent.recent_filenames_add(fname) 27 28 format = pymol.importing.filename_to_format(fname)[2] 29 30 if fname[-4:] in ['.dcd', '.dtr', '.xtc', '.trr']: 31 load_traj_dialog(parent, fname) 32 elif format in ('aln', 'fasta'): 33 load_aln_dialog(parent, fname) 34 elif format == 'mae': 35 load_mae_dialog(parent, fname) 36 elif format == 'ccp4': 37 load_map_dialog(parent, fname, 'ccp4') 38 elif format == 'brix': 39 load_map_dialog(parent, fname, 'o') 40 elif format == 'mtz': 41 load_mtz_dialog(parent, fname) 42 else: 43 if format in ('pse', 'psw') and not ask_partial(parent, kwargs, fname): 44 return 45 46 if format in ('pml', 'py', 'pym'): 47 parent.cmd.cd(parent.initialdir, quiet=0) 48 49 try: 50 parent.cmd.load(fname, quiet=0, **kwargs) 51 except BaseException as e: 52 QtWidgets.QMessageBox.critical(parent, "Error", str(e)) 53 return 54 55 # auto-load desmond trajectory 56 if fname.endswith('-out.cms'): 57 for suffix in [ 58 ('_trj', 'clickme.dtr'), 59 ('.xtc',), 60 ]: 61 traj = os.path.join(fname[:-8] + suffix[0], *suffix[1:]) 62 if os.path.exists(traj): 63 load_traj_dialog(parent, traj) 64 break 65 66 return True 67 68 69def ask_partial(parent, kwargs, fname): 70 if kwargs.get('partial', 0) or not parent.cmd.get_names(): 71 return True 72 73 form = parent.load_form('askpartial') 74 form.check_rename.setChecked(parent.cmd.get_setting_boolean( 75 'auto_rename_duplicate_objects')) 76 77 if not form._dialog.exec_(): 78 return False 79 80 if form.check_partial.isChecked(): 81 kwargs['partial'] = 1 82 parent.cmd.set('auto_rename_duplicate_objects', 83 form.check_rename.isChecked(), quiet=0) 84 elif form.check_new.isChecked(): 85 parent.new_window([fname]) 86 return False 87 88 return True 89 90 91def load_traj_dialog(parent, filename): 92 '''Open a trajectory loading dialog''' 93 names = parent.cmd.get_object_list() 94 95 if not names: 96 msg = "To load a trajectory, you first need to load a molecular object" #noqa 97 QtWidgets.QMessageBox.warning(parent, "Warning", msg) 98 return 99 100 form = parent.load_form('load_traj') 101 form.input_object.addItems(names) 102 form.input_object.setCurrentIndex(form.input_object.count() - 1) 103 104 def get_command(*args): 105 command = '' 106 if form.input_dbm3.isChecked(): 107 command += 'set defer_builds_mode, 3\n' 108 command += ('load_traj \\\n' 109 ' %s, \\\n' 110 ' %s, %d, \\\n' 111 ' start=%d, stop=%d, interval=%d' % ( 112 filename, 113 form.input_object.currentText(), 114 form.input_state.value(), 115 form.input_start.value(), 116 form.input_stop.value(), 117 form.input_interval.value())) 118 return command 119 120 def update_output_command(*args): 121 form.output_command.setText(get_command()) 122 123 def run(): 124 parent.cmd.do(get_command()) 125 form._dialog.close() 126 127 # hook up events 128 form.input_object.currentIndexChanged.connect(update_output_command) 129 form.input_state.valueChanged.connect(update_output_command) 130 form.input_start.valueChanged.connect(update_output_command) 131 form.input_stop.valueChanged.connect(update_output_command) 132 form.input_interval.valueChanged.connect(update_output_command) 133 form.input_dbm3.toggled.connect(update_output_command) 134 form.button_ok.clicked.connect(run) 135 136 update_output_command() 137 form._dialog.setModal(True) 138 form._dialog.show() 139 140 141def load_mtz_dialog(parent, filename): 142 from pymol import headering 143 144 _fileData = headering.MTZHeader(filename) 145 146 FCols = _fileData.getColumnsOfType("F") + \ 147 _fileData.getColumnsOfType("G") 148 PCols = _fileData.getColumnsOfType("P") 149 WCols = _fileData.getColumnsOfType("W") + \ 150 _fileData.getColumnsOfType("Q") 151 _2FC, _2PC, _looksLike = _fileData.guessCols("2FoFc") 152 _FC, _PC, _looksLike = _fileData.guessCols("FoFc") 153 154 form = parent.load_form('load_mtz') 155 form.input_amplitudes.addItems(FCols) 156 form.input_phases.addItems(PCols) 157 form.input_weights.addItem("") 158 form.input_weights.addItems(WCols) 159 form.input_prefix.setFocus(), 160 161 for col in [_2FC, _FC]: 162 if col in FCols: 163 form.input_amplitudes.setCurrentIndex(FCols.index(col)) 164 break 165 for col in [_2PC, _PC]: 166 if col in PCols: 167 form.input_phases.setCurrentIndex(PCols.index(col)) 168 break 169 170 if _fileData.reso_min is not None: 171 form.input_reso_min.setValue(_fileData.reso_min) 172 if _fileData.reso_max is not None: 173 form.input_reso_max.setValue(_fileData.reso_max) 174 175 def run(): 176 try: 177 parent.cmd.load_mtz(filename, 178 form.input_prefix.text(), 179 form.input_amplitudes.currentText(), 180 form.input_phases.currentText(), 181 form.input_weights.currentText(), 182 form.input_reso_min.value(), 183 form.input_reso_max.value(), 184 quiet=0) 185 except BaseException as e: 186 QtWidgets.QMessageBox.critical(parent, "Error", str(e)) 187 188 form._dialog.accepted.connect(run) 189 form._dialog.setModal(True) 190 form._dialog.show() 191 192 193def load_aln_dialog(parent, filename): 194 _self = parent.cmd 195 196 import numpy 197 import difflib 198 import pymol.seqalign as seqalign 199 200 try: 201 alignment = seqalign.aln_magic_read(filename) 202 except ValueError: 203 # fails for fasta files which don't contain alignments 204 _self.load(filename) 205 return 206 207 # alignment record ids and PyMOL model names 208 ids = [rec.id for rec in alignment] 209 ids_remain = list(ids) 210 models = _self.get_object_list() 211 models_remain = list(models) 212 mapping = {} 213 214 N = len(ids) 215 M = len(models) 216 217 # ids -> models similarity matrix 218 similarity = numpy.zeros((N, M)) 219 for i in range(N): 220 for j in range(M): 221 similarity[i, j] = difflib.SequenceMatcher(None, 222 ids[i], models[j], False).ratio() 223 224 # guess mapping 225 for _ in range(min(N, M)): 226 i, j = numpy.unravel_index(similarity.argmax(), similarity.shape) 227 mapping[ids_remain.pop(i)] = models_remain.pop(j) 228 similarity = numpy.delete(similarity, i, axis=0) 229 similarity = numpy.delete(similarity, j, axis=1) 230 231 form = parent.load_form('load_aln') 232 comboboxes = {} 233 234 # mapping GUI 235 for row, rec_id in enumerate(ids, 1): 236 label = QtWidgets.QLabel(rec_id, form._dialog) 237 combobox = QtWidgets.QComboBox(form._dialog) 238 combobox.addItem("") 239 combobox.addItems(models) 240 combobox.setCurrentText(mapping.get(rec_id, "")) 241 form.layout_mapping.addWidget(label, row, 0) 242 form.layout_mapping.addWidget(combobox, row, 1) 243 comboboxes[rec_id] = combobox 244 245 def run(): 246 mapping = dict((rec_id, combobox.currentText()) 247 for (rec_id, combobox) in comboboxes.items()) 248 seqalign.load_aln_multi(filename, mapping=mapping, _self=_self) 249 form._dialog.close() 250 251 # hook up events 252 form.button_ok.clicked.connect(run) 253 form.button_cancel.clicked.connect(form._dialog.close) 254 255 form._dialog.setModal(True) 256 form._dialog.show() 257 258 259def load_mae_dialog(parent, filename): 260 form = parent.load_form('load_mae') 261 262 form.input_object_name.setPlaceholderText( 263 pymol.importing.filename_to_objectname(filename)) 264 form.input_object_props.setText(parent.cmd.get('load_object_props_default') or '*') 265 form.input_atom_props.setText(parent.cmd.get('load_atom_props_default') or '*') 266 267 def get_command(*args): 268 command = ('load \\\n %s' % (filename)) 269 name = form.input_object_name.text() 270 if name: 271 command += ', \\\n ' + name 272 command += ', \\\n mimic=' + ('1' if form.input_mimic.isChecked() else '0') 273 command += ( 274 ', \\\n object_props=%s' 275 ', \\\n atom_props=%s' % ( 276 form.input_object_props.text(), 277 form.input_atom_props.text())) 278 multiplex = [-2, 0, 1][form.input_multiplex.currentIndex()] 279 if multiplex != -2: 280 command += ', \\\n multiplex={}'.format(multiplex) 281 return command 282 283 def update_output_command(*args): 284 form.output_command.setText(get_command()) 285 286 def run(): 287 parent.cmd.do(get_command()) 288 form._dialog.close() 289 290 # hook up events 291 form.input_multiplex.currentIndexChanged.connect(update_output_command) 292 form.input_mimic.stateChanged.connect(update_output_command) 293 form.input_object_name.textChanged.connect(update_output_command) 294 form.input_object_props.textChanged.connect(update_output_command) 295 form.input_atom_props.textChanged.connect(update_output_command) 296 form.button_ok.clicked.connect(run) 297 298 update_output_command() 299 form._dialog.setModal(True) 300 form._dialog.show() 301 302 303def load_map_dialog(parent, filename, format='ccp4'): 304 form = parent.load_form('load_map') 305 normalize_setting = 'normalize_' + format + '_maps' 306 307 form.input_object_name.setText( 308 pymol.importing.filename_to_objectname(filename)) 309 form.input_normalize.setChecked(parent.cmd.get_setting_int(normalize_setting) > 0) 310 311 def get_command(*args): 312 command = 'set %s, %d\n' % (normalize_setting, 313 1 if form.input_normalize.isChecked() else 0) 314 command += 'load ' + filename 315 name = form.input_object_name.text() 316 if name: 317 command += ', \\\n ' + name 318 else: 319 name = pymol.importing.filename_to_objectname(filename) 320 321 selesuffix = '' 322 323 level = round(form.input_level.value(), 4) 324 sele = form.input_selection.currentText() 325 if sele: 326 buf = form.input_buffer.value() 327 selesuffix += ', %s, %s' % (sele, buf) 328 if form.check_carve.isChecked(): 329 selesuffix += ', carve=%s' % (buf) 330 331 if form.check_volume.isChecked(): 332 name_volume = form.input_name_volume.text() or (name + '_volume') 333 command += '\nvolume %s, %s, %s blue .5 %s yellow 0' % ( 334 name_volume, name, level, level * 2) 335 command += selesuffix 336 337 if form.check_isomesh.isChecked(): 338 name_isomesh = form.input_name_isomesh.text() or (name + '_isomesh') 339 command += '\nisomesh %s, %s, %s' % (name_isomesh, name, level) 340 command += selesuffix 341 342 if form.check_isosurface.isChecked(): 343 name_isosurface = form.input_name_isosurface.text() or (name + '_isosurface') 344 command += '\nisosurface %s, %s, %s' % (name_isosurface, name, level) 345 command += selesuffix 346 347 return command 348 349 def update_output_command(*args): 350 form.output_command.setText(get_command()) 351 352 def run(): 353 parent.cmd.do(get_command()) 354 form._dialog.close() 355 356 # hook up events 357 form.input_normalize.stateChanged.connect(update_output_command) 358 form.input_object_name.textChanged.connect(update_output_command) 359 form.check_volume.stateChanged.connect(update_output_command) 360 form.check_isomesh.stateChanged.connect(update_output_command) 361 form.check_isosurface.stateChanged.connect(update_output_command) 362 form.check_carve.stateChanged.connect(update_output_command) 363 form.input_name_volume.textChanged.connect(update_output_command) 364 form.input_name_isomesh.textChanged.connect(update_output_command) 365 form.input_name_isosurface.textChanged.connect(update_output_command) 366 form.input_selection.editTextChanged.connect(update_output_command) 367 form.input_level.valueChanged.connect(update_output_command) 368 form.input_buffer.valueChanged.connect(update_output_command) 369 form.button_ok.clicked.connect(run) 370 371 update_output_command() 372 form._dialog.setModal(True) 373 form._dialog.show() 374 375 376def _get_assemblies(pdbid): 377 # TODO move to another module 378 import json 379 pdbid = pdbid.lower() 380 url = "https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/" + pdbid 381 try: 382 data = json.load(urllib.urlopen(url)) 383 assembies = data[pdbid][0]['assemblies'] 384 return [a['assembly_id'] for a in assembies] 385 except LookupError: 386 pass 387 except Exception as e: 388 print('_get_assemblies failed') 389 print(e) 390 return [] 391 392 393def _get_chains(pdbid): 394 # TODO move to another module 395 url = "http://www.rcsb.org/pdb/rest/describeMol?structureId=" + pdbid 396 try: 397 from lxml import etree 398 except ImportError: 399 from xml.etree import ElementTree as etree 400 try: 401 data = etree.parse(urllib.urlopen(url)) 402 return [e.get('id') for e in data.findall('./*/polymer/chain')] 403 except Exception as e: 404 print('_get_chains failed') 405 print(e) 406 return [] 407 408 409def file_fetch_pdb(parent): 410 form = parent.load_form('fetch') 411 form.input_assembly.setEditText(parent.cmd.get('assembly')) 412 413 def get_command(*args): 414 code = form.input_code.text() 415 if len(code) != 4: 416 return '' 417 418 def get_name(w): 419 name = w.text() 420 if name: 421 return ', ' + name 422 return '' 423 424 if form.input_check_pdb.isChecked(): 425 command = 'set assembly, "%s"\nfetch %s%s%s' % ( 426 form.input_assembly.currentText(), 427 code, form.input_chain.currentText(), 428 get_name(form.input_name)) 429 else: 430 command = '' 431 432 if form.input_check_2fofc.isChecked(): 433 command += '\nfetch %s%s, type=2fofc' % ( 434 code, get_name(form.input_name_2fofc)) 435 436 if form.input_check_fofc.isChecked(): 437 command += '\nfetch %s%s, type=fofc' % ( 438 code, get_name(form.input_name_fofc)) 439 440 return command 441 442 def update_output_command(*args): 443 form.output_command.setText(get_command()) 444 445 def code_changed(code): 446 for combo in [form.input_assembly, form.input_chain]: 447 if combo.count() != 1: 448 text = combo.currentText() 449 combo.clear() 450 combo.addItem('') 451 combo.setEditText(text) 452 if len(code) == 4: 453 update_assemblies(code) 454 update_chains(code) 455 update_output_command() 456 457 def run(): 458 if len(form.input_code.text()) != 4: 459 QtWidgets.QMessageBox.warning(parent, "Error", "Need 4 letter PDB code") 460 return 461 parent.cmd.do(get_command()) 462 form._dialog.close() 463 464 # async events 465 update_assemblies = AsyncFunc(_get_assemblies, form.input_assembly.addItems) 466 update_chains = AsyncFunc(_get_chains, form.input_chain.addItems) 467 468 # hook up events 469 form.input_code.textChanged.connect(code_changed) 470 form.input_chain.editTextChanged.connect(update_output_command) 471 form.input_assembly.editTextChanged.connect(update_output_command) 472 form.input_name.textChanged.connect(update_output_command) 473 form.input_name_2fofc.textChanged.connect(update_output_command) 474 form.input_name_fofc.textChanged.connect(update_output_command) 475 form.input_check_pdb.stateChanged.connect(update_output_command) 476 form.input_check_2fofc.stateChanged.connect(update_output_command) 477 form.input_check_fofc.stateChanged.connect(update_output_command) 478 form.button_ok.clicked.connect(run) 479 480 update_output_command() 481 form._dialog.show() 482 483 484def file_save(parent): 485 form = parent.load_form('save_molecule') 486 default_selection = form.input_selection.currentText() 487 488 get_setting_int = parent.cmd.get_setting_int 489 form.input_no_pdb_conect_nodup.setChecked( not get_setting_int('pdb_conect_nodup')) 490 form.input_pdb_conect_all.setChecked( get_setting_int('pdb_conect_all')) 491 form.input_no_ignore_pdb_segi.setChecked( not get_setting_int('ignore_pdb_segi')) 492 form.input_pdb_retain_ids.setChecked( get_setting_int('pdb_retain_ids')) 493 form.input_retain_order.setChecked( get_setting_int('retain_order')) 494 495 models = parent.cmd.get_object_list() 496 selections = parent.cmd.get_names('public_selections') 497 names = models + selections 498 499 form.input_state.addItems(map(str, range(1, parent.cmd.count_states() + 1))) 500 501 form.input_selection.addItems(names) 502 form.input_selection.lineEdit().setPlaceholderText(default_selection) 503 504 formats = [ 505 'PDBx/mmCIF (*.cif *.cif.gz)', 506 'PDB (*.pdb *.pdb.gz)', 507 'PQR (*.pqr)', 508 'MOL2 (*.mol2)', 509 'MDL SD (*.sdf *.mol)', 510 'Maestro (*.mae)', 511 'MacroModel (*.mmd *.mmod *.dat)', 512 'ChemPy Pickle (*.pkl)', 513 'XYZ (*.xyz)', 514 'MMTF (*.mmtf)', 515 'By Extension (*.*)', 516 ] 517 518 @PopupOnException.decorator 519 def run(*_): 520 selection = form.input_selection.currentText() or default_selection 521 state = int(form.input_state.currentText().split()[0]) 522 523 parent.cmd.set('pdb_conect_nodup', not form.input_no_pdb_conect_nodup.isChecked()) 524 parent.cmd.set('pdb_conect_all', form.input_pdb_conect_all.isChecked()) 525 parent.cmd.set('ignore_pdb_segi', not form.input_no_ignore_pdb_segi.isChecked()) 526 parent.cmd.set('pdb_retain_ids', form.input_pdb_retain_ids.isChecked()) 527 parent.cmd.set('retain_order', form.input_retain_order.isChecked()) 528 529 if form.input_multi_state.isChecked(): 530 fmt = form.input_multi_state_fmt.text() 531 elif form.input_multi_object.isChecked(): 532 fmt = form.input_multi_object_fmt.text() 533 else: 534 fmt = '' 535 536 if fmt and form.input_multi_prompt.isChecked(): 537 fss = parent.cmd.multifilenamegen(fmt, selection, state) 538 else: 539 fss = [(fmt, selection, state)] 540 541 for fname, selection, state in fss: 542 fname = getSaveFileNameWithExt(parent, 543 'Save Molecule As...', 544 os.path.join(parent.initialdir, fname), 545 filter=';;'.join(formats)) 546 547 if not fname: 548 return 549 550 parent.initialdir = os.path.dirname(fname) 551 552 if form.input_multisave.isChecked(): 553 parent.cmd.multisave(fname, selection, state, quiet=0) 554 elif '{' in os.path.basename(fname): 555 parent.cmd.multifilesave(fname, selection, state, quiet=0) 556 else: 557 parent.cmd.save(fname, selection, state, quiet=0) 558 parent.recent_filenames_add(fname) 559 560 form._dialog.close() 561 562 form.input_multi_state.pressed.connect( 563 lambda: form.input_state.setCurrentIndex(1)) 564 565 form.button_ok.clicked.connect(run) 566 form._dialog.show() 567 568 569def file_save_png(parent): #noqa 570 if parent.dialog_png is not None: 571 parent.dialog_png.show() 572 return 573 574 form = parent.load_form('png') 575 parent.dialog_png = form._dialog 576 577 def run(): 578 from pymol import exporting 579 fname = getSaveFileNameWithExt(parent, 'Save As...', parent.initialdir, 580 filter='PNG File (*.png)') 581 if not fname: 582 return 583 584 parent.initialdir = os.path.dirname(fname) 585 586 rendering = form.input_rendering.currentIndex() 587 ray = 0 588 width, height, dpi = 0, 0, -1 589 590 ''' 591 dpi = float(form.input_dpi.currentText()) 592 593 width = exporting._unit2px( 594 form.input_width.value(), dpi, 595 form.input_width_unit.currentText()) 596 height = exporting._unit2px( 597 form.input_height.value(), dpi, 598 form.input_height_unit.currentText()) 599 ''' 600 601 form._dialog.hide() 602 603 if rendering == 1: 604 parent.cmd.do('draw %d, %d' % (width, height)) 605 width = 0 606 height = 0 607 elif rendering == 2: 608 parent.cmd.do('set opaque_background, 1') 609 ray = 1 610 elif rendering == 3: 611 parent.cmd.do('set opaque_background, 0') 612 ray = 1 613 614 parent.cmd.sync() 615 parent.cmd.do( 616 'png %s, %d, %d, %d, ray=%d' % 617 (fname, width, height, dpi, ray)) 618 619 ''' 620 def units_changed(): 621 dpi = float(form.input_dpi.currentText()) 622 width_unit = form.input_width_unit.currentText() 623 height_unit = form.input_width_unit.currentText() 624 if dpi < 1 and (width_unit != 'px' or height_unit != 'px'): 625 form.input_dpi.setCurrentIndex(1) 626 dpi = float(form.input_dpi.currentText()) 627 628 # initial values 629 form.input_width.setValue(pymol.cmd.get_viewport()[0]) 630 631 dpi_index = 0 632 dpi_values = [-1, 150, 300] 633 dpi = pymol.cmd.get_setting_int('image_dots_per_inch') 634 635 if dpi > 0: 636 try: 637 dpi_index = dpi_values.index(dpi) 638 except ValueError: 639 dpi_values.append(dpi) 640 dpi_values.sort() 641 dpi_index = dpi_values.index(dpi) 642 643 for dpi in dpi_values: 644 form.input_dpi.addItem(str(dpi)) 645 form.input_dpi.setCurrentIndex(dpi_index) 646 647 # hook up events 648 form.input_width_unit.currentIndexChanged.connect(units_changed) 649 form.input_height_unit.currentIndexChanged.connect(units_changed) 650 ''' 651 form.button_ok.clicked.connect(run) 652 653 form._dialog.show() 654 655 656def file_save_mpeg(parent, _preselect=None): 657 form = parent.load_form('movieexport') 658 659 filters = { 660 'png': 'Numbered PNG Files (*.png)', 661 'mp4': 'MPEG 4 movie file (*.mp4)', 662 'mpg': 'MPEG 1 movie file (*.mpg *.mpeg)', 663 'mov': 'QuickTime (*.mov)', 664 'gif': 'Animated GIF (*.gif)', 665 } 666 667 support = { 668 '': {'mp4': 0, 'mpg': 0, 'mov': 0, 'gif': 0}, 669 'ffmpeg': {'mp4': 1, 'mpg': 1, 'mov': 1, 'gif': 1}, 670 'mpeg_encode': {'mp4': 0, 'mpg': 1, 'mov': 0, 'gif': 0}, 671 'convert': {'mp4': 0, 'mpg': 0, 'mov': 0, 'gif': 1}, 672 } 673 674 from pymol.movie import find_exe as has_exe 675 676 def update_encoder_options(): 677 encoder = form.input_encoder.currentText() 678 679 for fmt, enabled in support[encoder].items(): 680 w = getattr(form, 'format_' + fmt) 681 w.setEnabled(enabled) 682 if not enabled and w.isChecked(): 683 if encoder == 'mpeg_encode': 684 form.format_mpg.setChecked(True) 685 elif encoder == 'convert': 686 form.format_gif.setChecked(True) 687 else: 688 form.format_png.setChecked(True) 689 690 form.input_quality.setEnabled(encoder not in ("", "convert")) 691 692 if encoder and not has_exe(encoder): 693 msg = "Encoder '%s' is not installed." % encoder 694 pkg = None 695 url = None 696 if not pkg: 697 QtWidgets.QMessageBox.warning(parent, "Warning", msg) 698 else: 699 from pymol.Qt import utils 700 utils.conda_ask_install(pkg[1], pkg[0], msg, url=url) 701 702 if _preselect == 'png': 703 form.format_png.setChecked(True) 704 form.group_format.hide() 705 else: 706 for i in range(1, form.input_encoder.count()): 707 encoder = form.input_encoder.itemText(i) 708 if has_exe(encoder): 709 form.input_encoder.setCurrentIndex(i) 710 break 711 712 if _preselect == 'mov': 713 encoder = 'ffmpeg' 714 form.input_encoder.setCurrentIndex(1) 715 form.format_mov.setChecked(True) 716 elif encoder == 'ffmpeg': 717 form.format_mp4.setChecked(True) 718 else: 719 form.format_mpg.setChecked(True) 720 721 form._dialog.adjustSize() 722 723 @PopupOnException.decorator 724 def run(*_): 725 for fmt in filters: 726 w = getattr(form, 'format_' + fmt) 727 if w.isChecked(): 728 break 729 fname = getSaveFileNameWithExt(parent, 730 'Save As...', parent.initialdir, 731 filter=filters[fmt]) 732 if not fname: 733 return 734 735 parent.initialdir = os.path.dirname(fname) 736 737 if fmt == 'png': 738 parent.cmd.mpng(fname, 739 width=form.input_width.value(), 740 height=form.input_height.value(), 741 mode=2 if form.input_ray.isChecked() else 1, 742 quiet=0, modal=-1) 743 else: 744 mode = 'ray' if form.input_ray.isChecked() else 'draw' 745 encoder = form.input_encoder.currentText() 746 747 parent.cmd.movie.produce(fname, 748 width=form.input_width.value(), 749 height=form.input_height.value(), 750 quality=form.input_quality.value(), 751 mode=mode, encoder=encoder, 752 quiet=0) 753 754 def set_resolution(height): 755 w, h = form.input_width.value(), max(1, form.input_height.value()) 756 aspect = (w / h) if (w and h) else 9999 757 width = int(round(min(aspect, 16. / 9.) * height / 2.)) * 2 758 form.input_width.setValue(width) 759 form.input_height.setValue(height) 760 761 # initial values 762 viewport = parent.cmd.get_viewport() 763 form.input_width.setValue(viewport[0]) 764 form.input_height.setValue(viewport[1]) 765 form.input_quality.setValue(parent.cmd.get_setting_int('movie_quality')) 766 if parent.cmd.get_setting_int('ray_trace_frames'): 767 form.input_ray.setChecked(True) 768 update_encoder_options() 769 770 # hook up events 771 form.button_ok.clicked.connect(run) 772 form.input_encoder.currentIndexChanged.connect(update_encoder_options) 773 774 for height in (720, 480, 360): 775 button = getattr(form, 'button_%dp' % height) 776 button.pressed.connect(lambda h=height: set_resolution(h)) 777 778 form._dialog.show() 779 780 781def _file_save_object(self, otype, formats, noobjectsmsg): 782 names = self.cmd.get_names_of_type(otype) 783 784 if not names: 785 QtWidgets.QMessageBox.warning(self, "Warning", noobjectsmsg) 786 return 787 788 form = self.load_form('save_object') 789 form.input_name.addItems(names) 790 form._dialog.setWindowTitle('Save ' + otype) 791 792 def run(): 793 name = form.input_name.currentText() 794 795 fname = getSaveFileNameWithExt(self, 796 'Save As...', 797 self.initialdir, 798 filter=';;'.join(formats)) 799 800 if not fname: 801 return 802 803 self.cmd.save(fname, name, -1, quiet=0) 804 form._dialog.close() 805 806 form.button_ok.clicked.connect(run) 807 form._dialog.show() 808 809 810def file_save_map(self): 811 return _file_save_object(self, 'object:map', ['CCP4 (*.ccp4 *.map)'], 812 'No map objects loaded') 813 814 815def file_save_aln(self): 816 url = "http://pymolwiki.org/index.php/Align#Alignment_Objects" 817 return _file_save_object(self, 'object:alignment', ['clustalw (*.aln)'], 818 'No alignment objects loaded\n\n' 819 'Hint: create alignment objects with "align" and\n' 820 '"super" using the "object=..." argument.') 821