File I/O
Everything the desktop PyMOL GUI does with the filesystem and the network: open/load, format-specific import dialogs, save/export, sessions (.pse/.psw), partial session merge,
recent files, log files and script execution, image export, movie export, and fetch from the PDB.
All references are path:line in packages/engine/, which is unmodified upstream. Anything that
could not be verified is marked UNVERIFIED.
Where the port stands. packages/bridge/tenmol_bridge/panels/files.py serves the whole
surface as cmd.tenmol_files.<method>, typed in packages/protocol/src/topics/files.ts and
called from apps/web/src/features/files/filesApi.ts. The dialogs are
LoadDialogs.tsx, SaveDialogs.tsx, ImageDialogs.tsx, ToolsDialogs.tsx, PathPicker.tsx,
FileDropTarget.tsx, globalDrop.ts and PluginDialogHost.tsx in the same directory.
0. The model
Server-managed working directory, a client-rendered server-side path picker, and upload/download as opt-in escape hatches. No native OS dialogs. Grounded in how the code actually works:- Every load/save path in PyMOL is a real server-side path string.
cmd.loadcalls_self.exp_path(filename)(packages/engine/modules/pymol/importing.py:751) which expands~and$VARS(packages/engine/modules/pymol/cmd.py:112), then hands the path to C (packages/engine/modules/pymol/internal.py:362).cmd.savedoes the same (packages/engine/modules/pymol/exporting.py:836-838). Feeding it browserFileblobs would require rewriting every loader. - Format dispatch is done on filename/extension, not content
(
packages/engine/modules/pymol/importing.py:41-109,packages/engine/modules/pymol/exporting.py:836-844). A browser upload loses the path but keeps the name, so uploads must be materialised into a server temp/working dir beforecmd.load. - Several operations cannot be expressed as a download at all:
mpngwrites N numbered files (packages/engine/modules/pymol/moving.py:366),movie.producecreates a.tmpsibling directory and shells out toffmpeg/mpeg_encode/convert(packages/engine/modules/pymol/movie.py:770-800, 946-985),log_openholds an open file handle for the whole session (packages/engine/modules/pymol/commanding.py:107-155),fetchwrites intofetch_path(packages/engine/modules/pymol/importing.py:1213-1248), andcd/system/runoperate on the server process (packages/engine/modules/pymol/externing.py:32,112,packages/engine/modules/pymol/parsing.py:427). - The GUI already has a notion of a sticky “current directory”:
initialdir(packages/engine/modules/pmg_qt/pymol_qt_gui.py:496-506) which defaults toos.getcwd()and is updated after every browse. That maps 1:1 onto a server-managed working directory exposed in React.
1. Menu inventory (File menu)
Source:packages/engine/modules/pymol/_gui.py:80-133 (toolkit-independent menu data, consumed by
packages/engine/modules/pmg_qt/pymol_qt_gui.py:295-357).
Keyboard:
Ctrl+O → file_open, Ctrl+S → session_save
(packages/engine/modules/pmg_qt/pymol_qt_gui.py:387-388). Note these are extra Qt shortcuts, not
PyMOL set_key bindings.
Note: PyMOLDesktopGUI also declares file_autoload_mtz (_gui.py:13) but it is not
referenced by any menu entry and has no implementation in pmg_qt — dead slot.
2. Cross-cutting infrastructure
2.1 initialdir
packages/engine/modules/pmg_qt/pymol_qt_gui.py:496-506. Falls back to os.getcwd() until the first
browse, then remembers the last directory used by any dialog. Updated in
file_dialogs.py:40, 585, 619, 669, 770 and pymol_qt_gui.py:669, 749, 834, 843, 862.
Web: one server-side session.initialdir value on the bridge, exposed as
fs.get_initialdir / fs.set_initialdir, seeded from cmd.pwd().
2.2 Recent files
packages/engine/modules/pymol/_gui.py:975-1032. SQLite DB at ~/.pymol/recent.db, table
recent(filename text unique, timestamp integer), REPLACE INTO on add, pruned to
~15-20 entries when count > 20 (_gui.py:1026-1031). Menu is rebuilt on
aboutToShow and truncates display to '...' + fname[-120:] when ≥128 chars
(pymol_qt_gui.py:367-375). Registered by load_dialog (file_dialogs.py:42),
molecule export (file_dialogs.py:593) and session save-as (pymol_qt_gui.py:671).
Web: keep the same SQLite file server-side; expose recent.list / recent.add.
React renders a dropdown; clicking runs the same load_dialog pipeline.
2.3 Drag & drop
- Onto the 3D viewport:
packages/engine/modules/pmg_qt/pymol_gl_widget.py:256-270— accepts URLs, local files viatoLocalFile(), remote URLs passed through as strings, each goes togui.load_dialog(url). - Onto the command line:
packages/engine/modules/pmg_qt/pymol_qt_gui.py:1092-1119— inserts the path as text at the cursor. - macOS Finder “Open With”:
PyMOLApplication.handle_file_open_active(pymol_qt_gui.py:1140-1160): opens a new instance unless--reuse_helper, honoursauto_reinitialize, and for.pswsetspresentation,internal_gui 0,internal_feedback 0,full_screen onbeforeload_dialog. Web: HTML5 drop on the canvas. Local files → upload endpoint → path →load_dialog. URLs (text/uri-list) → pass the string straight tocmd.load, which supports URLs viafile_read(packages/engine/modules/pymol/internal.py:279-297). The Finder/new-window behaviour has no web analogue (see risks).
2.4 Qt form loading (load_form)
packages/engine/modules/pmg_qt/pymol_qt_gui.py:512-546 loads pmg_qt/forms/<name>.ui|.py. Every
.ui under packages/engine/modules/pmg_qt/forms/ that this area owns is enumerated in §3/§4:
askpartial.ui, fetch.ui, load_aln.ui, load_mae.ui, load_map.ui,
load_mtz.ui, load_traj.ui, movieexport.ui, png.ui, render.ui,
save_molecule.ui, save_object.ui.
Web: each becomes a React component; there is no runtime .ui loading.
2.5 Save-dialog extension helper
getSaveFileNameWithExt (packages/engine/modules/pymol/Qt/utils.py:229-246): if the typed basename has
no ., append the first *.ext from the selected filter. Reproduce exactly in the React
save picker.
2.6 Error surfacing
PopupOnException (packages/engine/modules/pymol/Qt/utils.py:323-348) wraps file_save,
file_save_mpeg, session_save_as, _file_save, file_run. Load errors are shown via
QMessageBox.critical (file_dialogs.py:67-69, 197).
Web: a single <ErrorToast>/modal fed by bridge error events.
3. Loading
3.1 Open… entry point
file_open (pymol_qt_gui.py:643-649): getOpenFileNames with no filter (all
files), multi-select; first file loads with partial=0, all subsequent with
partial=1, and the loop breaks as soon as load_dialog returns falsy.
3.2 load_dialog dispatch table
packages/engine/modules/pmg_qt/file_dialogs.py:33-77:
_get_cms_traj_file (:12-30) tries <stem>_trj/clickme.dtr then <stem>.xtc, where
stem strips -out.cms or .cms.
3.3 Format detection
filename_to_format (packages/engine/modules/pymol/importing.py:41-109) returns
(prefix, ext, format, zipped). Rules to port verbatim:
.gz/.bz2are stripped intozippedand the previous extension re-parsed (:45-49).brick|callback|cgo|model|plugin→ format''(reserved loadable names, not extensions) (:56-58).ent|p5m→pdb;pze→pse+gz;pzw→psw+gz;mmd|out|dat→mmod;cc2→cc1;sd→sdf;sdfgz→sdf+gz;rst7→rst;o|dsn6|omap→brix;maegz→mae+gz;ph4→moe;spi→spider;pym|pyc→py;p1m|pim→pml;xml→pdbml;mmcif→cif;bcif;bcifgz→bcif+gz;pdb\d+→pdb;xyz_\d+→xyz;dxbin→dx(:59-105).filename_to_objectname(:37-39) = prefix run throughcmd.get_legal_name.
3.4 cmd.load core
packages/engine/modules/pymol/importing.py:643-827. Signature
load(filename, object='', state=0, format='', finish=1, discrete=-1, quiet=1, multiplex=None, zoom=-1, partial=0, mimic=1, object_props=None, atom_props=None).
Behaviour worth preserving:
- numeric
formataccepted (loadable enum,constants.py:9-63) (:719-726). format=…strvariants are deprecated in favour ofload_raw(:727-733).format='plugin:<name>'syntax (:743-744).- object name defaults to filename prefix, or
get_unused_name('obj'); fordcd/dtrit defaults to the most recently added object (:754-759, helper:126-132). - unknown extensions fall through to VMD molfile plugins via
_cmd.find_molfile_plugin(:762-767). .trjAMBER-vs-GROMACS/NetCDF autodetection by magic bytes (:770-773,:115-124)..crdAMBER-vs-CHARMM autodetection by two leading*lines (:775-778,:617-624).- dispatch through
loadfunctionswith signature introspection; acontentsparameter causescmd.file_read(filename)to be called first (:780-827).
loadfunctions (:1619-1644) — the complete supported-format-with-python-handler list:
mae(incentive→raises), pdbml, cml, mtz(→load_mtz, raises IncentiveOnly at
:1511), py(→run), pml(→@), pwg, aln, fasta, png(→load_png), idx,
pse, psw, ply, r3d, cc1, pdb(→read_pdbstr), stl, dae, plus incentive-only
vis, moe, phypo. Everything else goes to pymol.internal._load
(packages/engine/modules/pymol/internal.py:346-386) and thence to C.
3.5 Other load entry points
3.6 Format-specific import dialogs
3.6.1 Trajectory — load_traj_dialog (file_dialogs.py:102-149, form load_traj.ui)
Guard: if cmd.get_object_list() is empty, warn “To load a trajectory, you first need
to load a molecular object” and abort (:104-109).
Widgets (forms/load_traj.ui): input_object (combo of object names, preselects the
last), input_state (spin, 0-999, default 1, tooltip “Append if state=0”),
input_start (1-99999, default 1), input_stop (-1-99999, default -1, “Load entire
trajectory if stop < 1”), input_interval (min 1, default 1), input_dbm3 (checkbox
“defer_builds_mode=3”), output_command (live command preview), button_ok (“Load”).
Emits (:115-129): optional set defer_builds_mode, 3 then
load_traj <file>, <object>, <state>, start=, stop=, interval=, executed via cmd.do.
3.6.2 Alignment — load_aln_dialog (file_dialogs.py:204-282, form load_aln.ui)
- Parses via
pymol.seqalign.aln_magic_read; a FASTA with <2 records or ragged lengths raisesValueError→ falls back to plaincmd.load(→fab-based extended structures) (:211-222). - Builds an id→object similarity matrix with
difflib.SequenceMatcher+numpy, greedy argmax assignment to pre-fill the mapping (:236-247). - UI: one row per alignment record —
QLabel(rec_id)+QComboBoxof object names (blank first), added intoform.layout_mapping(:253-261). - OK →
seqalign.load_aln_multi(filename, mapping=…)(:263-267). - Cancel on a FASTA file → asks “Load sequences as extended structures instead?” and if
Yes runs
cmd.load(:269-275).
3.6.3 Maestro — load_mae_dialog (file_dialogs.py:285-333, form load_mae.ui)
Widgets: input_object_name (placeholder = filename_to_objectname),
input_mimic (checkbox, default on, “Use settings to match cartoon/ribbon color and
ballstick style”), input_object_props / input_atom_props (default from settings
load_object_props_default / load_atom_props_default, both "*",
SettingInfo.h:818-819), input_multiplex combo with 4 entries mapping to
(multiplex, discrete) = (-2,-1) (0,0) (0,1) (1,-1) (:304-313), output_command,
button_ok. In this open-source build loadfunctions['mae'] raises
IncentiveOnlyException (importing.py:1620, :31-33) — the dialog exists but the
load fails.
3.6.4 Map — load_map_dialog (file_dialogs.py:336-406, form load_map.ui)
Called for ccp4/map (normalize setting normalize_ccp4_maps) and brix/o
(normalize_o_maps) (:52-55, :338; settings SettingInfo.h:210,394).
Widgets: input_object_name, input_normalize (checkbox “normalize (mean=0 stdev=1)”),
input_selection (editable combo: blank/enabled/sele/center), input_buffer
(double, default 2.0), check_carve, input_level (double, 4 decimals, min −99, step
0.1, default 1.0), check_volume + input_name_volume, check_isomesh +
input_name_isomesh, check_isosurface + input_name_isosurface, output_command,
button_ok.
Generated script (:344-380): set <normalize_setting>, 0|1 → load <file>[, name] →
optionally volume <name>_volume, <map>, <level> blue .5 <level*2> yellow 0,
isomesh <name>_isomesh, <map>, <level>, isosurface …, each with the shared suffix
, <sele>, <buffer>[, carve=<buffer>].
3.6.5 Reflections (MTZ) — load_mtz_dialog (file_dialogs.py:152-201, form load_mtz.ui)
Header parsed by pymol.headering.MTZHeader (packages/engine/modules/pymol/headering.py:132-262);
column type filters F/G (amplitudes), P (phases), W/Q (weights); guessCols("2FoFc")
and guessCols("FoFc") preselect (:157-179). Resolution spinboxes seeded from
reso_min/reso_max (:181-184). OK → cmd.load_mtz(filename, prefix, amplitudes, phases, weights, reso_min, reso_max, quiet=0). cmd.load_mtz raises
IncentiveOnlyException (importing.py:1481-1511) in this build.
Legacy Tk twin: packages/engine/modules/pmg_tk/PyMOLMapLoad.py — a Pmw.Dialog with OK/Cancel/Help,
groups “Column Labels” (_ampl_chooser, _phase_chooser, _wt_chooser with a None
entry), “Input Options” (_min_res_fld, _max_res_fld, real validators),
“Map Options” (_name_prefix_fld alphanumeric, _fofc_chooser checkbutton “FoFc”).
It supports MTZ/CIF/CNS-HKL headers (PyMOLMapLoad.py:28-33) and on OK calls
cmd.map_generate(pfx, file, ampl, phases, weights, min_res, max_res, 1, 1)
(:281-283, impl packages/engine/modules/pymol/creating.py:176-274), then builds a representation
according to default_fofc_map_rep / default_2fofc_map_rep
(SettingInfo.h:758-759, defaults "volume") → isosurface (level 1.0), isomesh
(±3.0 green/red for FoFc, 1.0 blue for 2FoFc), or volume (:294-331), wrapped in
set suspend_updates (:295,335), and auto-closes if autoclose_dialogs
(SettingInfo.h:761, default 1). This dialog is not wired to any menu in the Qt GUI
(no references outside its own file) — it is the design reference for a real
map_generate UI, which cmd.map_generate does support in this build.
3.6.6 Session partial-load gate — ask_partial (file_dialogs.py:80-99, form askpartial.ui)
Skipped when partial is already truthy or the session is empty (cmd.get_names())
(:81-82). Radio options (askpartial.ui):
check_discard(default) — “Discard current session / All currently loaded objects will be deleted.”check_partial— “Merge with current session (partial load)”; enablescheck_rename(“Automatically rename duplicate objects”, bound to global settingauto_rename_duplicate_objects,SettingInfo.h:661) which is written back withcmd.set(..., quiet=0)(:91-94).check_new— “Open in new PyMOL Window” →parent.new_window([fname]), returns False so nothing is loaded locally (:95-97). Modalexec(); Cancel aborts the load (:88-89).
4. Saving / exporting
4.1 cmd.save core
packages/engine/modules/pymol/exporting.py:784-935. Signature
save(filename, selection='(all)', state=-1, format='', ref='', ref_state=-1, quiet=1, partial=0).
- selection preprocessed by
selector.process(:830). - format guessed from extension; unrecognised extension raises “Unrecognized file
format” (
:836-843) — note the docstring at:809-812still claims a PDB fallback. pse/pswset thesession_filesetting with\→/normalisation (:846-849).- Python-object formats via
func_type4:mmod(io.mmd.toFile),pkl(binary pickle),pkla(ascii pickle) (:853-857, 900-902). - Everything else dispatches through
savefunctionswith signature introspection; if the handler takesfilenameit is assumed to have written the file itself, otherwise the returned str/bytes are written here, honouring.gz(gzip) and.bz2(bz2) (:861-925).
savefunctions (:988-1020) — complete export format list:
Plus the extension-only formats handled by
func_type4: mmd/out/dat (→mmod),
pkl, pkla. pmo is explicitly rejected in multisave (:641-642).
String-returning API equivalents (perfect for browser download without touching disk):
get_str (:666), get_bytes (:679), get_pdbstr (:222), get_cifstr (:937),
get_xyzstr (:949), get_sdfstr (:952), get_mol2str (:955), get_pqrstr
(:963), get_maestr (:966), get_fastastr (:170), get_alnstr (:958),
get_ccp4str (:969), get_psestr (:975).
4.2 Export Molecule dialog — file_save (file_dialogs.py:519-601, form save_molecule.ui)
Widgets:
input_selection— editable combo seeded withenabled,allfrom the.ui, then all objects +cmd.get_names('public_selections')(:530-537); its placeholder is the original first entry and is used when left blank (:521,555).input_state— combo-1 (current),0 (all states), then1..count_states()(:534); parsed withint(text.split()[0])(:556).- Tab “Options”/
tab_3:input_retain_order← settingretain_order. - Tab PDB/
tab_4:input_no_pdb_conect_nodup(invertedpdb_conect_nodup),input_pdb_conect_all,input_no_ignore_pdb_segi(invertedignore_pdb_segi),input_pdb_retain_ids,input_multisave(“Write HEADER for every object”). All five settings are read on open (:523-528) and written back on OK (:558-562) — settings ids atSettingInfo.h:821,423,204,389,351. - Tab “Multiple files”/
tab: radiosinput_multi_off(default) /input_multi_object(+input_multi_object_fmt, default{name}) /input_multi_state(+input_multi_state_fmt, default{name}_{state}), andinput_multi_prompt(“Prompt for every file”, default on). Pressinginput_multi_stateforces the state combo to index 1 = “0 (all states)” (:597-598). button_ok“Save…”.
:539-551): PDBx/mmCIF *.cif *.cif.gz; PDB *.pdb *.pdb.gz;
PQR *.pqr; MOL2 *.mol2; MDL SD *.sdf *.mol; Maestro *.mae; MacroModel
*.mmd *.mmod *.dat; ChemPy Pickle *.pkl; XYZ *.xyz; MMTF *.mmtf;
By Extension *.*.
Execution (:564-595): if a multi-file pattern is active and “prompt for every file”
is checked, cmd.multifilenamegen(fmt, selection, state) produces one
(filename, selection, state) triple per object/state and a save dialog is shown for
each (:571-580); then per file: cmd.multisave if input_multisave, else
cmd.multifilesave if the basename still contains {, else cmd.save +
recent_filenames_add (:587-593).
Supporting APIs: multisave (exporting.py:604-657, pdb/cif only, append flag,
rejects gz/bz2), multifilesave (:707-732), multifilenamegen (:735-781,
placeholders {name} {state} {title} {num} {}, zero-padded {state}/{num}).
4.3 Export Map / Export Alignment — _file_save_object (file_dialogs.py:816-855, form save_object.ui)
Generic: cmd.get_names_of_type(otype); empty → warning box; else a combo input_name
and a “Save…” button; on OK a save dialog then cmd.save(fname, name, -1, quiet=0).
- Maps:
otype='object:map', filterCCP4 (*.ccp4 *.map), empty message “No map objects loaded” (:845-847). - Alignments:
otype='object:alignment', filterclustalw (*.aln), empty message “No alignment objects loaded\n\nHint: create alignment objects with “align” and “super” using the “object=…” argument.” (:850-855). (Aurllocal is assigned at:851but never used — dead code.)
4.4 Geometry/scene exports — _file_save (pymol_qt_gui.py:793-800, 802-821)
Single save dialog then cmd.save(fname, format=<fmt>, quiet=0):
VRML 2 WRL File (*.wrl) → wrl; COLLADA File (*.dae) → dae;
POV File (*.pov) → pov; STL File (*.stl) → stl; GLTF File (*.gltf) → gltf.
4.5 PNG export dialog — file_save_png (file_dialogs.py:604-688, form png.ui)
Singleton dialog (parent.dialog_png, :605-610, field declared
pymol_qt_gui.py:103). Widgets: a green banner label (“New in PyMOL 2.0: To render a
sized antialiased image, use the Draw/Ray panel in the upper right.”), input_rendering
combo, button_ok (“Save PNG image as …”).
Flow (:612-652): save dialog (PNG File (*.png)) → initialdir update → dialog hides
→ branch on the combo index:
- 0 “capture current display” →
png <f>, 0, 0, -1, ray=0 - 1 “draw antialiased OpenGL image” →
draw 0, 0first, thenpngwith width/height 0 - 2 “ray trace with opaque background” →
set opaque_background, 1,ray=1 - 3 “ray trace with transparent background” →
set opaque_background, 0,ray=1thencmd.sync()andcmd.do('png %s, %d, %d, %d, ray=%d'). Width/height/DPI inputs are dead code, commented out at:625-634and:654-685(they would have usedexporting._unit2px). The live sizing UI is the render panel (§4.6).
cmd.png (exporting.py:499-602): appends .png when missing, exp_path, dpi default
from image_dots_per_inch (SettingInfo.h:529), _unit2px accepts px/in/mm/cm
suffixes (:478-497), prior (-1 try / 0 no / 1 yes) fetches the last rendered image
without re-rendering, format 0=PNG 1=PPM guessed from .ppm (:553-567), and
non-ray renders go through _call_with_opengl_context (:602).
4.6 Draw/Ray render panel — render_dialog (pymol_qt_gui.py:673-790, form render.ui)
Docked in the upper-right by default (called with a widget). Page 1 widgets:
input_width/input_height (px spinboxes, max 99999), input_width_units/
input_height_units (cm/inch doubles), input_units combo (cm, inch),
input_dpi editable combo (300,150,90, seeded from image_dots_per_inch, int
validator), button_current (“Reset” → viewport size), button_lock (“Lock aspect
ratio”, default on), input_transparent (“transparent background (“Ray” only)”, default
on), button_draw (“Draw (fast)”), button_ray (“Ray (slow)”).
Bidirectional px↔units↔dpi conversion with a re-entrancy guard UpdateLock
(:676-717). run_draw issues draw W, H; run_ray sets opaque_background from the
transparency checkbox and issues ray W, H, async=1, then switches the stack to page 2
(:730-742). Page 2: button_save (“Save Image to File” → save dialog +
cmd.png(fname, prior=1, dpi=…)), button_clip (“Copy Image to Clipboard” →
_copy_image), button_back (”< Back”).
_copy_image (pymol_qt_gui.py:1170-1186) writes a temp PNG with prior=1 and pushes
it to the Qt clipboard; the generic hook is cmd._copy_image
(packages/engine/modules/pymol/internal.py:272-274, monkey-patched at pymol_qt_gui.py:1242), also
reachable as cmd.copy_image (exporting.py:35-36).
Underlying: cmd.draw (viewing.py:1601-1660, stops movie/sculpting, needs a GL
context via _call_with_opengl_context :1660) and cmd.ray
(viewing.py:1662-1745, internal _ray :1581; stops movie/rocking/sculpting
:1733-1739, renderer=1 shells out to PovRay).
4.7 Movie export dialog — file_save_mpeg (file_dialogs.py:691-813, form movieexport.ui)
Widgets: group “Movie Format” with input_encoder combo (“, ffmpeg, mpeg_encode,
convert), input_quality spin (60-100%, default from movie_quality setting,
SettingInfo.h:734), radios format_mp4, format_mpg, format_mov, format_gif,
format_png (default checked); size group input_width/input_height (max 9999,
seeded from cmd.get_viewport()), preset buttons button_720p/button_480p/
button_360p; group “Rendering” radios input_draw (default) / input_ray (checked if
ray_trace_frames, SettingInfo.h:114); button_ok (“Save Movie as …”).
Encoder capability matrix (:702-707): none→png only; ffmpeg→mp4/mpg/mov/gif;
mpeg_encode→mpg; convert→gif. Disabled radios auto-switch to the encoder’s default
(:711-726), quality is disabled for ""/convert (:725), and a missing binary pops
“Encoder ‘<x>’ is not installed.” (:727-735, pymol.movie.find_exe
packages/engine/modules/pymol/movie.py:824-844). Preselect logic for the three menu entries at
:737-754; _preselect='png' hides the whole format group.
Resolution presets clamp to ≤16:9 and round width to an even number (:789-794).
Save filters (:694-700): Numbered PNG Files (*.png), MPEG 4 movie file (*.mp4),
MPEG 1 movie file (*.mpg *.mpeg), QuickTime (*.mov), Animated GIF (*.gif).
Run (:758-787): PNG → cmd.mpng(fname, width, height, mode=2|1, quiet=0, modal=-1);
otherwise → cmd.movie.produce(fname, width, height, quality, mode='ray'|'draw', encoder, quiet=0).
cmd.mpng (packages/engine/modules/pymol/moving.py:366-434, internal _mpng
internal.py:243-267): writes <prefix>NNNN.png (or .ppm), strips an existing numeric
suffix, first/last/preserve/mode/modal.
movie.produce (packages/engine/modules/pymol/movie.py:846-1000): creates <basename>.tmp/, renders
frames there via mpng, then _encode (:687-806) runs mpeg_encode (via
pymol.mpeg_encode, PPM frames, quality→1-30, frame rate snapped to
[23.976,24,25,29.97,30,50,59.94,60]), ffmpeg (two-pass palette for GIF; libvpx-vp9
for .webm; crf 10/15/20 by quality; -pix_fmt yuv420p), or ImageMagick convert
(-delay). Even dimensions forced for mp4/mov/webm (:952-967); temp dir deleted
unless preserve (:805-806); keep_alive set/unset (:975, :804).
5. Sessions
- Save Session (
pymol_qt_gui.py:651-655): reads thesession_filesetting (SettingInfo.h/Setting.cpp:660), passes it throughcmd.as_pathstr(cmd.py:116-125) and delegates to Save-As; if empty, a dialog is shown. - Save Session As (
pymol_qt_gui.py:656-671): filtersPyMOL Session File (*.pse *.pze *.pse.gz)andPyMOL Show File (*.psw *.pzw *.psw.gz); always callscmd.save(fname, format='pse', quiet=0)(so.pswgets identical content, only the extension differs), thenrecent_filenames_add. - Serialisation:
get_psestr→cmd.get_session→cPickle.dumps(session, 1)(exporting.py:975-979).get_session(:371-476) honourspse_export_version(SettingInfo.h:855, backports settings/objects via_session_convert_legacy:261-369),pse_binary_dump(:859), deprecatedsession_compression(:649, now warns:469-473),session_cache_optimize(:696) →cache('optimize'), plus_session_save_taskshooks (:436-451). - Loading:
load_pse(importing.py:829-854) reads the file withcmd.file_read, unpickles,set_session(..., steal=1), setssession_file(unix separators), and for.psw(orpresentation+presentation_auto_start,SettingInfo.h:512) rewinds the movie and recalls the first scene. set_session(importing.py:138-183): accepts bytes (zlib+pickle) or dict,partialandcacheflags, restorespymol.session/_cache, runs_session_restore_tasks, and activates thesecuritywizard when the session contains movie commands (:178-180; wizard textpackages/engine/modules/pymol/wizard/security.py:15-43with accept/decline/mdump).- Partial / merge: §3.6.6 plus the
partialkwarg threaded fromfile_open(pymol_qt_gui.py:645-649). - Session chaining:
cmd.chain_session(viewing.py:934-959) finds the next numbered.pse/.pswnext tosession_fileand loads it aspsw— used by presentation mode.
6. Logs, scripts, working directory
7. Network / fetch
7.1 Get PDB… dialog — file_fetch_pdb (file_dialogs.py:444-516, form fetch.ui)
Widgets: banner label linking to the fetch_path wiki page; input_code (max 4 chars,
placeholder “4 letter PDB code”); checkboxes + object-name fields
input_check_pdb/input_name (checked by default), input_check_2fofc/
input_name_2fofc, input_check_fofc/input_name_fofc; group “PDB Structure Options”
with editable combos input_chain and input_assembly (seeded from the assembly
setting, SettingInfo.h:857); output_command live preview; button_ok (“Download”).
On a 4-character code, two async network lookups populate the combos
(AsyncFunc, packages/engine/modules/pymol/Qt/utils.py:100-125):
_get_assemblies→https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/<id>→assemblies[].assembly_id(file_dialogs.py:409-423)_get_chains→https://www.ebi.ac.uk/pdbe/api/pdb/entry/polymer_coverage/<id>→molecules[].chains[].chain_id(:426-441) Generated command (:448-475):set assembly, "<a>"+fetch <code><chain>[, name], plusfetch <code>[, name], type=2fofcand…type=fofc. OK validates the 4-letter code (:492-495) and runs viacmd.do.
7.2 cmd.fetch
importing.py:1331-1402. Args: code, name, state, finish, discrete, multiplex, zoom, type, async_, path, file, quiet. path defaults to the fetch_path setting or '.'
(:1387-1389; setting default ".", SettingInfo.h:607, special-cased in
packages/engine/layer1/Setting.cpp:644). async_<0 ⇒ async when interactive (:1390-1391).
_multifetch (:1274-1329) splits whitespace-separated codes, infers type (2-3 char
codes → cc chemical component; else fetch_type_default, default "cif",
SettingInfo.h:870), understands EMD-xxxx/emd_xxxx/CID_/SID_ prefixes, extracts
a trailing chain from 5+ character codes and post-filters with cmd.remove, and
legalises the object name.
_fetch (:1155-1272): per-type filename pattern ({code}.{type},
{code}_{type}.ccp4, emd_{code}.ccp4, {type}_{code}.sdf, {code}.cif), skips the
download when the target file already exists (:1219-1221), tries each URL from
fetch_host (space-separated; aliases pdb/pdbe/pdbj, :1118-1122; setting default
"pdb", SettingInfo.h:736) crossed with hostPaths (:1124-1153: mmtf, bio, pdb,
cif, bcif, 2fofc, fofc, pubchem, emd, cc), treats an HTML body as failure
(:1229-1231), writes the file, then loads via cmd.load / read_pdbstr /
load_raw('cif'|'mmtf').
Related: download_chem_comp (internal.py:314-338) caches ligand CIFs in fetch_path
and warns when it is read-only.
7.3 URL loading
cmd.file_read (internal.py:279-311) accepts a filename, an URL (://, sets a
PyMOL/<version> User-Agent) or an open handle, and transparently gunzips/bunzips by
magic bytes. load_dialog deliberately skips the initialdir update for URLs
(file_dialogs.py:39-40).
8. Per-user state files (also “file I/O”)
9. Behaviour, per menu entry
The bridge FS API
None of this existed upstream: the only directory-ish primitives arecmd.ls (prints only,
externing.py:73-110) and cmd.system, and neither returns structured data.
packages/bridge/tenmol_bridge/panels/files.py installs a cmd.tenmol_files namespace, so every
method is an ordinary {t:'call', fn:'cmd.tenmol_files.<method>'}:
pwd, chdir, home, expand, initialdir / set_initialdir, browse, stat, mkdir,
glob_paths, places, recent / recent_add, classify, note_open, plan_open,
load_formats, load_capabilities, save_formats, unavailable, refused, plus the
per-format dialog descriptors (traj_dialog_info, map_dialog_info, aln_dialog_info,
mae_dialog_info, mtz_dialog_info, map_generate_info) and produce /
multifilenamegen for movie export. Bulk bytes ride POST /upload and GET /blob/{id} on the
HTTP side rather than the WebSocket.
pymol.pymolhttpd.PymolHttpd (packages/engine/modules/pymol/pymolhttpd.py:441-520) serves a
document root and JSON-wrapped cmd calls and was read as prior art; it is a separate legacy
server and is not used (see docs/cmd-api-rpc.md §5).
10. Constraints this area lives under
.pwgfiles launch a second HTTP server and canlaunch <module>arbitrary Python, open a browser, and evenos.unlinkthemselves (importing.py:516-615)..pwgis refused (panels/files.py::refused).- Session security wizard:
.psefiles with movie commands trigger a modal accept/decline flow (importing.py:178-180,wizard/security.py). It is a normal wizard, so it renders through the generic wizard protocol (docs/wizards.md§7.24); without it, sessions would silently execute embedded commands. run/@/system/cdare full local code execution. Acceptable for a local desktop replacement, catastrophic if the bridge ever binds to a non-loopback interface — which is why the transport is loopback + token +Originallow-list (docs/cmd-api-rpc.md§8.1).- Movie/mpng and log files are inherently server-side; any UX that implies “save to my Downloads folder” will be wrong for them.
- Blocking dialogs:
ask_partialusesexec()(file_dialogs.py:88) and thetkinter.filedialogshim is blocking.panels/files.py::DialogBrokersuspends the calling Python thread on a round trip without deadlocking thecmdlock (_self.lockcm), andinstall_tk_filedialogputs the same contract behindmimic_tk._qtFileDialog’s API so legacy plugins keep working. - Formats that don’t work in this build:
maeload,load_mtz,vis,moe,phypoall raiseIncentiveOnlyException(importing.py:31-33,1620,1641-1643,1511);.mtlexport raises (exporting.py:981-986);pmois rejected (exporting.py:641-642). The React dialogs must not advertise them as working, or must surface the exception cleanly. - Dead UI that may be mistaken for a spec: PNG width/height/DPI block
(
file_dialogs.py:625-685),PyMOLMapLoad.py(Tk, unreferenced),file_autoload_mtz(_gui.py:13, no implementation), unusedurlinfile_save_aln(file_dialogs.py:851). new_windowspawns a new OS process (_gui.py:41-53) and is reachable from the File menu and from the partial-session dialog (file_dialogs.py:96). One backend process means that path is not offered.- Encoder availability (
ffmpeg,mpeg_encode,convert) is probed withshutil.whichon the server (movie.py:824-844), so the export dialog asks the server for capabilities rather than assuming (cmd.get_movie_encoders). - Large transfers:
.pseof a big system, CCP4 maps and MP4s are tens to hundreds of MB, so they ride/bloband/uploadrather than the WebSocket JSON channel. cmd.savethrows on unknown extensions (exporting.py:841-843) despite the docstring’s PDB-fallback claim, so the extension is validated before the call.- Path encoding:
cmd.as_pathstr/exp_path(cmd.py:112-125) do Windows-specific decoding and$VAR/~expansion, so the picker sends raw strings and lets the server expand them (cmd.tenmol_files.expand) rather than pre-resolving.