Skip to main content

Chemvas — 2D chemical structure drawing canvas

CI License: MIT Python 3.12+

English · 한국어

A lightweight, PyQt6-based 2D chemical structure drawing app for quickly sketching molecules and reaction schemes — and exporting publication-ready figures.

Chemvas — a Base/THF reaction scheme and several organocatalyst structures drawn on the canvas

Chemvas lets you combine molecular bonds/rings/labels, arrows, and bracket annotations on a single canvas. The default style follows the ACS 1996 conventions, and the goal is to draft figures for lab notebooks or papers fast. RDKit is an optional backend used for SMILES import, formula/weight calculation, and 2D→3D conversion — Chemvas runs without it.

Features

  • Bonds — single / double / triple, bold, wedge & hash; 30° angle snapping and a consistent default bond length.
  • Rings & templates — benzene, cycloalkanes, chair/boat conformers placed by live preview and click-to-insert.
  • Arrows — reaction, equilibrium, resonance, curved, and dashed arrows with adjustable width and head scale.
  • Brackets & annotations — square / round / curly brackets, dagger () and double dagger () annotation objects.
  • Atom labels — elements, charges, radicals, and common alias labels (Me, Et, OH, Ph, OMe, Boc, CO2Me, t-Bu, i-Pr, and the sulfonate/acyl groups OTs, Ts, OMs, Ms, OTf, Tf, Ns, OAc, Ac).
  • SMILES import (RDKit) — type a SMILES string, preview it under the cursor, and click to place it on the canvas.
  • MOL interchange — open MDL Molfiles (.mol, V2000) as new documents and export the selected structure as .mol. Import and plain-element export need no RDKit; abbreviation labels require optional RDKit expansion. Property records are limited to M CHG / M RAD, wedge/hash stereo to single bonds, and the counts-line chiral flag to zero. Singlet M RAD code 1 is rejected until the annotation model can preserve spin multiplicity.
  • Molecule Info window (RDKit) — 3D preview (drag to rotate, scroll to zoom), molecular formula and weight, and one-click copy of the canonical SMILES, InChI, and InChIKey for the current selection.
  • Figure export — plain SVG / PDF / PNG / TIFF with outlined glyphs (so screen, vector, and raster output never diverge) and deterministic physical sizing (bond-length or 84 / 174 mm column fit), independent of zoom. Editable Chemvas SVG is opt-in and embeds the source document in SVG metadata.
  • 2D→3D .xyz export (RDKit) — convert the current molecule or atom/bond selection into 3D coordinates; charges/radicals and wedge/hash stereo are carried through, and common alias labels are expanded into explicit fragments.
  • Editing — select / move, an eraser tool (click or drag to erase), horizontal & vertical flip, perspective rotation, and delta-based undo/redo.
  • Desktop menus — standard File / Edit / View menus, including a Canvas Size dialog for the sheet size and orientation.
  • ChemDraw-compatible shortcuts — a substantial subset (see below).
  • Save / load.chemvas JSON documents preserve the full working state.
  • Autosave & recovery — open documents are snapshotted every few seconds, so an unexpected exit costs you almost nothing: the next launch restores your unsaved work and reopens your last session automatically. Unsaved tabs show a marker, the File menu keeps an Open Recent list, and reopening an already-open file switches to its window instead of duplicating it.

Install

Requires Python 3.12+ and PyQt6.

pip install chemvas

# optional: enable SMILES / formula / 3D features
pip install "chemvas[rdkit]"

Or install from a clone of this repo — append ".[rdkit]" for the optional features:

python -m pip install -e .

Prebuilt one-file desktop binaries are still on the roadmap (see below).

Running

python app/main.py    # development tree
chemvas               # after install
chemvas --help        # root CLI help without starting Qt
chemvas --version     # package version without starting Qt

Pick a tool from the top toolbar and click/drag on the canvas to draw. Enter a SMILES string in the top input and press Insert to enter placement mode: move the mouse to preview, click to insert, Esc to cancel. Templates work the same preview-and-click way.

Examples

Open examples/template1.chemvas via File ▸ Open to explore the document shown above — a reaction scheme plus several organocatalyst structures.

File format

File ▸ Save / Open works with .chemvas files — a JSON-based format holding the molecule model, annotations, arrows, bracket annotations, and settings:

{ "type": "chemvas", "version": 6, "state": { /* ... */ } }

Version 6 can also carry an optional Calculation Plan v2 with bounded precomplex candidates, exact XYZ provenance, and explicit endpoint review selections. Version 5 Calculation Plan v1 documents and versions 1-4 remain readable.

Figure export defaults to plain SVG without Chemvas source metadata. Choose Editable Chemvas SVG only when you want the SVG to carry the original document payload for round-tripping back into Chemvas.

Autosave & recovery

Chemvas snapshots every open document to a per-user app-data folder every few seconds — nothing is written next to your own files. If the app is killed or crashes, the next launch restores those documents (unsaved ones flagged with a and a status-bar note); a clean quit simply reopens whatever files were open. Snapshots are pruned once a session has been restored or closed cleanly.

3D export & Molecule Info

  • Export scope is the current chemical graph or the current atom/bond selection. Arrows, bracket annotations, and free text are not included in .xyz.
  • +/-/radical marks become formal charges / radical electrons; wedge/hash bonds on single bonds become RDKit stereochemistry hints.
  • Alias labels expand into explicit fragments (e.g. OTs → the full -O-S(=O)(=O)-C6H4-CH3 tosylate). Each alias attaches through a single bond; Ns is the para (4-nitrobenzenesulfonyl) isomer. Carbon-bound PPh3 is accepted only through exactly one ordinary covalent single bond and expands as phosphonium C-[P+](Ph)3; standalone, non-carbon, multiple, non-single, styled, or explicitly charge/radical-annotated uses fail closed.
  • Unsupported labels, mis-connected aliases, and invalid wedge/hash use fail with an explicit error message instead of guessing.
  • .xyz stores element symbols and 3D coordinates only — it is not a full round-trip of bond orders, stereochemistry, or reaction semantics.

Agent-safe document editing

Headless document rendering

An agent can render the complete drawing through the same figure-export path as the desktop app without opening a window or loading RDKit:

chemvas render-document scheme.chemvas --output scheme.svg
chemvas render-document scheme.chemvas --output scheme.png --dpi 600
chemvas render-document scheme.chemvas --output scheme-transparent.png \
  --background transparent

The output suffix selects SVG or PNG. White is the default background; PNG DPI may be 150, 300, 600, or 1200, while SVG ignores DPI. The command starts only an invisible offscreen Qt canvas, does not start session recovery, and leaves the source untouched. It refuses existing files, directories, and symlinks and publishes the new output atomically.

Standard output is a deterministic JSON report containing the exact source and output SHA-256 hashes, document version, output byte count, physical point size, and PNG pixel dimensions. Repeated renders are byte-identical within the same Chemvas/Qt/font environment; Qt or font changes can alter path geometry or encoded bytes, so consumers should use the reported hash rather than assume cross-platform byte identity. Rendering is fail-closed at 8 MiB of source data, 20,000 graphics records, 64 MiB of output, 14,400 points per side, and—for PNG—10,000 pixels per side or 25 million total pixels.

Graph Patch v1

An agent can inspect every stable atom ID and then propose a bounded Graph Patch without starting Qt or rewriting the whole .chemvas document:

chemvas inspect-document scheme.chemvas > inspection.json
chemvas apply-patch scheme.chemvas patch.json --dry-run
chemvas apply-patch scheme.chemvas patch.json --output revised.chemvas

inspect-document reports the exact source-file SHA-256, document version, next_atom_id, complete atom/bond inventory, effective charge/radical annotations, connected components, and dependent scene-state counts. The agent copies that exact hash into a Graph Patch v1 precondition:

{
  "format": "chemvas-graph-patch",
  "version": 1,
  "source_sha256": "<64 lowercase hexadecimal characters>",
  "operations": [
    {"op": "add_atom", "atom_id": 12, "element": "O",
     "x": 216.0, "y": 72.0, "color": "#000000", "explicit_label": true},
    {"op": "add_bond", "a": 4, "b": 12, "order": 1,
     "style": "single", "color": "#000000"},
    {"op": "update_bond", "a": 4, "b": 12,
     "changes": {"order": 2, "style": "double"}}
  ]
}

Supported operations are add_atom, update_atom (element/color/explicit label), move_atom, add_bond, update_bond, and remove_bond. Operations run in order on a private copy and publish only after full document and Calculation Plan validation. move_atom also moves dependent ring-fill, bound-mark, and perspective coordinates. Dry-run performs the identical validation and reports the candidate file hash but writes nothing. Apply preserves the input document version, never changes the source, and refuses to replace an existing file or symlink.

Graph Patch v1 deliberately does not delete atoms or edit charge/radical annotations, arrows, groups, or Calculation Plans. It makes no chemical or mechanistic inference; use the GUI or a separately reviewed plan update for those semantics.

Headless structure inspection

Installed Chemvas can expose structures to an agent without starting Qt:

chemvas inspect scheme.chemvas

inspect needs no RDKit and prints a JSON inventory of connected components with stable atom IDs, formal charges, and annotation totals. Machine handoff of geometries happens exclusively through the elementary-step machine.json published by pack-step below; there is no separate per-species bundle format.

Calculation states and elementary steps

Draw the reactant, product, catalyst, and spectators on one canvas, then open Calculation ▸ Edit States and Steps.... For each endpoint, assign every connected component one of these inclusion modes:

  • included: enters the XYZ geometry, electron count, charge, and multiplicity validation;
  • context_only: records a catalyst, solvent, additive, or other condition but does not enter the calculation coordinates.

Roles (reactant, product, catalyst, spectator) belong to a step endpoint, not globally to a structure. A state can therefore be S01's product and S02's reactant without changing the state itself. Including a component as an endpoint's own reactant or product disables it on the opposite endpoint (a consumed species is not present on both sides), while catalysts and spectators stay editable on both. The atom-correspondence table lists only included reactant atoms and offers same-element product atoms by stable Chemvas ID. Suggest by structure (RDKit) fills the unmapped atoms of the maximum common substructure; bond orders are matched loosely, so a reaction center whose bonds only change order (e.g. C-O → C=O) is suggested too, and only atoms whose connectivity breaks or forms are left for you. It never overwrites a mapping you made and is a review-only starting point, not an automated mechanism inference. While the dialog is open, each included atom is labelled with its Chemvas ID on the drawing. Mapped reactant atoms are blue, mapped product atoms are orange, and unmapped atoms stay gray, so mapping progress is visible on the structure. Exact IDs shared by both endpoints, such as a drawn catalyst reused on both sides, are suggested once; they are not inferred by element or position, and an explicit Unmapped choice is preserved. Duplicate product mappings are rejected. The GUI saves an incomplete table as a draft, while its mapped/total status stays blocked until every included atom on both endpoints has a complete one-to-one source map. This status covers the source mapping gate; RDKit geometry generation and downstream chemical review are still separate requirements. The labels are temporary overlays: closing the dialog removes them without changing the drawing, the current canvas selection, or undo history.

Agents can attach and inspect the same contract without Qt:

chemvas attach-plan scheme.chemvas plan.json --output mechanism.chemvas
chemvas inspect-plan mechanism.chemvas
chemvas pack-step mechanism.chemvas --step S01 --output calculations/machine.json

For a step with exactly two included components on each endpoint, generate and review bounded rigid-placement candidates before packing:

chemvas generate-precomplex mechanism.chemvas precomplex-request.json \
  --step S01 --output mechanism-candidates.chemvas
chemvas inspect-precomplex mechanism-candidates.chemvas --step S01
chemvas select-precomplex mechanism-candidates.chemvas --step S01 \
  --reactant-candidate <candidate-id> --product-candidate <candidate-id> \
  --reviewer <reviewer> --output mechanism-reviewed.chemvas
chemvas pack-step mechanism-reviewed.chemvas --step S01 \
  --output calculations/machine.json

The strict request binds generation to the exact input through source_document_sha256 and step_id, names one intercomponent contact per endpoint, records an explicit gas-phase or solvent environment, and sets a retained candidate cap. Generation writes a new version-6 document with Calculation Plan v2 and selection: null; inspect-precomplex exposes IDs, provenance, validation metrics, hashes, and exact XYZ. select-precomplex records one reactant/product pair with the same reviewer and timestamp and binds each selection to its XYZ hash. Before handoff, pack-step deterministically regenerates both bounded ensembles from the current graph, plan, RDKit provenance, contacts, and profile and rejects any mismatch. Placement scores are geometric clash and contact metrics, not energies or stability rankings. Unreviewed or partially reviewed multicomponent endpoints remain blocked.

Request format v1 is frozen to the legacy chemvas-rigid-precomplex-placement/1 profile so existing requests and persisted ensembles remain byte-reproducible. New generation requests use format v2 and must explicitly name "profile": "chemvas-rigid-precomplex-placement/2". Profile 2 uses the covalent radii from Cordero et al., Table 2 (C sp3 and low-spin Fe/Co entries) and the van der Waals radii from Alvarez, Table 1 for every supported element. The ensemble, generation/inspection reports, and final machine.json placement metadata carry the profile, dataset IDs, DOIs, and an exact radius-table hash. Profile 1 retains its original mixed, partly unverified van der Waals values only for reproduction; it is not a scientific reference table.

Profile 2 remains within document version 6 and Calculation Plan v2. Chemvas versions predating this profile fail closed on it and cannot open such a document; use Chemvas 0.2.0 or newer for profile-2 files. These cited radii and Chemvas's thresholds still define a deterministic geometric heuristic: designated contacts use 0.85 × the covalent-radius sum; other pairs use the larger of 1.05 × the covalent-radius sum and 0.60 × the van der Waals-radius sum; soft-overlap scoring uses 0.85 × the van der Waals-radius sum. This is not a hard-sphere physical model, energy, or stability claim. Fe/Co spin and coordination are not represented in the current input model, so the documented low-spin selector is fixed rather than inferred. Researcher review and downstream quantum optimization remain required.

plan.json uses Calculation Plan v1. States own calculation membership and charge/multiplicity; step endpoints own roles:

{
  "format": "chemvas-calculation-plan",
  "version": 1,
  "states": [
    {"id": "R01", "charge": 0, "multiplicity": 1,
     "members": [
       {"component_atom_ids": [0, 1], "inclusion": "included"},
       {"component_atom_ids": [9], "inclusion": "context_only"}]},
    {"id": "P01", "charge": 0, "multiplicity": 1,
     "members": [{"component_atom_ids": [2, 3], "inclusion": "included"}]}
  ],
  "steps": [{
    "id": "S01",
    "reactant": {"state_id": "R01", "roles": [
      {"component_atom_ids": [0, 1], "role": "reactant"},
      {"component_atom_ids": [9], "role": "spectator"}]},
    "product": {"state_id": "P01", "roles": [
      {"component_atom_ids": [2, 3], "role": "product"}]},
    "atom_correspondence": [
      {"reactant_atom_id": 0, "product_atom_id": 2},
      {"reactant_atom_id": 1, "product_atom_id": 3}]
  }]
}

Every component_atom_ids list must equal one complete connected component and must be sorted. pack-step atomically writes exactly one non-overwriting file named machine.json. It uses the shared factory/machine-observation v1 envelope and a chemistry/elementary-step v1 payload containing the source document hash, endpoint state and RDKit atom provenance, complete source/generated atom correspondence, and bond changes. Draw transferred hydrogens explicitly when implicit-hydrogen counts differ between endpoints; the generated atoms must also form a complete bijection.

inspect-plan reports a deterministic path_precheck for each step. When the source mapping is complete, both endpoints have the same charge and multiplicity, and either each endpoint is single-component or both multicomponent endpoints have an explicitly reviewed precomplex selection, the single artifact's endpoint_pair contains the exact reactant/product XYZ text and hashes. The product XYZ is rewritten into the reactant atom-identity order; the same object records that order and the bond-change reaction-center atoms as canonical 0-based indices. Downstream tools therefore do not need to reconstruct the mapping from element order or coordinates.

An incomplete source mapping still blocks pack-step without creating the output. Once that gate and the generated-atom bijection pass, an unreviewed multicomponent endpoint or electronic-state mismatch writes one observation with handoff.status: "blocked", namespaced handoff.codes, and payload.data.endpoint_pair: null. Chemvas does not invent contacts, select a candidate automatically, or treat generated coordinates as optimized minima. Reviewed generated coordinates remain initial guesses requiring downstream quantum optimization and scientific validation.

Keyboard shortcuts

Chemvas supports a major subset of ChemDraw-compatible shortcuts.

  • Empty canvas (tool hotkeys): Select/Marquee Space, Bond X, Atom A, Text T, Arrow E, Benzene J, Brackets Shift+T, Orbitals Shift+G, Chemical symbols Shift+E, Perspective Alt+D
  • Atom hotkeys (hover over an atom): element/alias labels c n o s p f h b i l m e r x d and Shift+f/p/a/b/s/n/e/z/m/l/o/q/h/y, charge +/-, edit label Enter, sprout 0/1/2/3/a/4/5/6/7/8/9/z/v/u (9 = gem-dimethyl)
  • Bond hotkeys (hover over a bond): Single 1, Double 2, Triple 3, Bold b/Shift+B, Wedge w, Hash h/Shift+H, Dashed d/Shift+D, double-bond position l/c/r, Benzene fusion a, Ring fusion 4/5/6/7/8, Chair fusion 9/0
  • Objects: Flip Horizontal Ctrl+Shift+H, Flip Vertical Ctrl+Shift+V, Rotate selection Alt+Up/Down (15°) and Alt+Left/Right (1°), Nudge selection Shift+Arrows (10 pt)
  • View: Actual size F5, Fit to window F6, Magnify F7, Reduce F8
  • File / edit: Save / Open / Undo / Redo (platform defaults), Ctrl+A (select all, switches to the Select tool), Ctrl+C (copy selection — PNG plus SVG/PDF vector clipboard flavors), Ctrl+X (cut selection), Ctrl+V (paste the copied selection), Ctrl+G / Ctrl+Shift+G (group / ungroup selection), Delete/Backspace (delete selection, or edit/delete the hovered atom/bond), Esc (cancel template / SMILES insertion)

Development

CI runs ruff, mypy, and the test suite headlessly (QT_QPA_PLATFORM=offscreen). See CONTRIBUTING.md for dev setup, how to run the tests, and the architecture conventions. Transitional UI code keeps its established *_ports / *_access / *_state / *_service boundaries where they still separate real responsibilities; new features do not copy that layout by default. The active boundaries are enforced by tests, so read CONTRIBUTING before restructuring anything.

The high-level design is in docs/ARCHITECTURE.md.

Roadmap / not yet supported

These are known gaps, not bugs — contributions welcome:

  • SDF (multi-molecule) interchange: import and export. Single-molecule .mol import/export, SMILES export ("copy as SMILES"), and InChI / InChIKey have landed.
  • Distribution: one-file desktop binaries (Chemvas is already on PyPI — pip install chemvas).
  • Multi-molecule / reaction-scheme 3D export and richer template libraries.
  • Deliberately out of scope for now: printing (export a PDF instead), persistent preferences (every document starts from the ACS 1996 defaults), pasting external clipboard content, and drag-and-drop file open.

License

MIT License

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chemvas-0.2.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chemvas-0.2.0-py3-none-any.whl (716.8 kB view details)

Uploaded Python 3

File details

Details for the file chemvas-0.2.0.tar.gz.

File metadata

  • Download URL: chemvas-0.2.0.tar.gz
  • Upload date:
  • Size: 1.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for chemvas-0.2.0.tar.gz
Algorithm Hash digest
SHA256 67b5084838b8b59364c1058da5b93fde0ea9cd8152e93d820afee4031e0419c3
MD5 77a686c30a3c677cee2be2db2dbb35c1
BLAKE2b-256 7822bd370993beefae0c1718e23322e536cf1c219c57997bc684384efe32a9c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for chemvas-0.2.0.tar.gz:

Publisher: release.yml on dhsohn/Chemvas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chemvas-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: chemvas-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 716.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for chemvas-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 adb213039d410d6ae142b12a5080ab01741d63e55b36c5cdc25a99a269fd3687
MD5 b18a88424bf939d21d550b478406c831
BLAKE2b-256 e88c811d5ee655e182ef4bb8385afe26c19056a91780c1887a909f7b64e1cd92

See more details on using hashes here.

Provenance

The following attestation bundles were made for chemvas-0.2.0-py3-none-any.whl:

Publisher: release.yml on dhsohn/Chemvas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.12.0

2 files

0.11.0

2 files

0.10.2

2 files

0.10.1

2 files

0.10.0

2 files

0.9.0

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page