English · 한국어
A lightweight, PyQt6-based 2D chemical structure drawing app for quickly sketching molecules and reaction schemes — and exporting publication-ready figures.
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 groupsOTs,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 toM CHG/M RAD, wedge/hash stereo to single bonds, and the counts-line chiral flag to zero. SingletM RADcode 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
.xyzexport (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 —
.chemvasJSON 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-CH3tosylate). Each alias attaches through a single bond;Nsis the para (4-nitrobenzenesulfonyl) isomer. Carbon-boundPPh3is accepted only through exactly one ordinary covalent single bond and expands as phosphoniumC-[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.
.xyzstores 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, BondX, AtomA, TextT, ArrowE, BenzeneJ, BracketsShift+T, OrbitalsShift+G, Chemical symbolsShift+E, PerspectiveAlt+D - Atom hotkeys (hover over an atom): element/alias labels
c n o s p f h b i l m e r x dandShift+f/p/a/b/s/n/e/z/m/l/o/q/h/y, charge+/-, edit labelEnter, sprout0/1/2/3/a/4/5/6/7/8/9/z/v/u(9= gem-dimethyl) - Bond hotkeys (hover over a bond): Single
1, Double2, Triple3, Boldb/Shift+B, Wedgew, Hashh/Shift+H, Dashedd/Shift+D, double-bond positionl/c/r, Benzene fusiona, Ring fusion4/5/6/7/8, Chair fusion9/0 - Objects: Flip Horizontal
Ctrl+Shift+H, Flip VerticalCtrl+Shift+V, Rotate selectionAlt+Up/Down(15°) andAlt+Left/Right(1°), Nudge selectionShift+Arrows(10 pt) - View: Actual size
F5, Fit to windowF6, MagnifyF7, ReduceF8 - 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
.molimport/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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67b5084838b8b59364c1058da5b93fde0ea9cd8152e93d820afee4031e0419c3
|
|
| MD5 |
77a686c30a3c677cee2be2db2dbb35c1
|
|
| BLAKE2b-256 |
7822bd370993beefae0c1718e23322e536cf1c219c57997bc684384efe32a9c1
|
Provenance
The following attestation bundles were made for chemvas-0.2.0.tar.gz:
Publisher:
release.yml on dhsohn/Chemvas
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chemvas-0.2.0.tar.gz -
Subject digest:
67b5084838b8b59364c1058da5b93fde0ea9cd8152e93d820afee4031e0419c3 - Sigstore transparency entry: 2444195502
- Sigstore integration time:
-
Permalink:
dhsohn/Chemvas@5c9c035e64b4326b44415106943943660ab68bc8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dhsohn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c9c035e64b4326b44415106943943660ab68bc8 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adb213039d410d6ae142b12a5080ab01741d63e55b36c5cdc25a99a269fd3687
|
|
| MD5 |
b18a88424bf939d21d550b478406c831
|
|
| BLAKE2b-256 |
e88c811d5ee655e182ef4bb8385afe26c19056a91780c1887a909f7b64e1cd92
|
Provenance
The following attestation bundles were made for chemvas-0.2.0-py3-none-any.whl:
Publisher:
release.yml on dhsohn/Chemvas
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chemvas-0.2.0-py3-none-any.whl -
Subject digest:
adb213039d410d6ae142b12a5080ab01741d63e55b36c5cdc25a99a269fd3687 - Sigstore transparency entry: 2444195563
- Sigstore integration time:
-
Permalink:
dhsohn/Chemvas@5c9c035e64b4326b44415106943943660ab68bc8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dhsohn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c9c035e64b4326b44415106943943660ab68bc8 -
Trigger Event:
push
-
Statement type: