Skip to content

Scoping

The Problem

When multiple structures are loaded, a command like /rep cartoon protein is ambiguous — which structure's protein should be shown as cartoon?

The Solution: Automatic Scoping

codemol maintains an active scope — the name of the currently active structure. When a scope is set, dispatch() automatically wraps selection arguments:

# Without scope:
cmd.show("cartoon", "polymer.protein")

# With scope = "1AKE":
cmd.show("cartoon", "model 1AKE and (polymer.protein)")

The user doesn't need to type model 1AKE and (...) — it's injected automatically.

How It Works

_scope_arg()

In tool_loader.py, _scope_arg() wraps a resolved selection with the model filter:

def _scope_arg(raw: str, resolved: str, scope: str) -> str:
    """Wrap selection: 'model X and (selection)'."""
    return f"model {scope} and ({resolved})"

Selection Detection

Not every argument should be scoped — numbers, filenames, and names should pass through. _is_selection() determines if an argument is a selection expression by checking for:

  • Alias matches (protein, ligand)
  • Selection keywords (chain, resi, within, etc.)
  • Chain/residue patterns (A/45/CA)
  • Logical operators (and, or, not)
_is_selection("protein")        # True — known alias
_is_selection("chain A")        # True — selection keyword
_is_selection("3.5")            # False — number
_is_selection("my_figure.png")  # False — filename

Tools That Skip Scoping

Some tools handle scoping themselves or should always run globally:

  • /io load — loads new structures, no scope to apply
  • /io clear — clears everything
  • /analysis align — takes two explicit structure names

Scope Lifecycle

graph TD
    A[Load structure A] -->|scope = A| B[Commands target A]
    B --> C[Load structure B]
    C -->|scope = B| D[Commands target B]
    D --> E[Click A in sidebar]
    E -->|scope = A| F[Commands target A again]
    F --> G[/io clear]
    G -->|scope = None| H[No scoping]
  1. First load — scope set to loaded structure
  2. Additional loads — scope switches to newest structure
  3. Sidebar click — scope changes to clicked structure via activate()
  4. Clear — scope reset to None

Default Arguments and Scoping

When a tool has a default argument like selection="all" and the user calls it without arguments, dispatch() replaces the default with the scope:

# Tool definition:
def run(cmd, selection: str = "all") -> str: ...

# User types: /surface area  (no args)
# Without scope: run(cmd, "all")
# With scope "1AKE": run(cmd, "model 1AKE")

This ensures that even commands without explicit selections respect the active structure.