Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ venv.bak/
.dmypy.json
dmypy.json

# claude
.claude
*/.claude
*/*/.claude

# Pyre type checker
.pyre/

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ pre-commit install

## Examples

### Colchicine (by name)
### Render Colchicine (by name)
```bash
$ chemscii colchicine --columns 100
```
![colchicine.png](examples/images/colchicine.png)


### Use chemscii as a Claude code tool
![claude_code_example.png](examples/images/claude_code_example.png)
17 changes: 9 additions & 8 deletions examples/claude_code/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# chemscii Claude Code Integration

When users ask about chemical structures, molecules, or compounds, render them using the `chemscii` CLI.
When users ask about chemical structures, molecules, drugs, or compounds, render them using the `chemscii` CLI.
If your response to a question includes SMILES, molecules, or ChEMBL IDs render them using the `chemscii` CLI.

## Default Rendering

Use the magic renderer (default) with 50 columns:
Use the magic renderer (default) with 100 columns:

```bash
chemscii "<molecule>"
chemscii "<molecule>" --columns 80
```

Examples:
- `chemscii "caffeine"` - render by common name
- `chemscii "aspirin"` - render by drug name
- `chemscii "CCO"` - render by SMILES notation
- `chemscii "CHEMBL25"` - render by ChEMBL ID
- `chemscii "caffeine" --columns 80` - render by common name
- `chemscii "aspirin" --columns 80` - render by drug name
- `chemscii "CCO" --columns 80` - render by SMILES notation
- `chemscii "CHEMBL25" --columns 80` - render by ChEMBL ID

## Larger Renderings

If users ask for a larger, bigger, or more detailed rendering, increase columns to 100:

```bash
chemscii "<molecule>" --columns 100
chemscii "<molecule>" --columns 120
```

## Alternative View
Expand Down
Binary file added examples/images/claude_code_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 7 additions & 16 deletions src/chemscii/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,6 @@ def parse_input(input_type: InputType, value: str) -> str | None:
return None


def version_callback(value: bool) -> None:
"""Print version and exit."""
if value:
console.print("chemscii 0.1.0")
raise typer.Exit()


@app.command()
def main(
molecule: str = typer.Argument(
Expand Down Expand Up @@ -137,6 +130,12 @@ def main(
"-c",
help="Output width for magic renderer.",
),
escape_codes: bool = typer.Option(
False,
"--codes",
"-e",
help="Include escape codes for color rendering after copying text.",
),
width: int = typer.Option(
60,
"--width",
Expand All @@ -149,14 +148,6 @@ def main(
"-H",
help="Canvas height for ascii/unicode renderers.",
),
version: bool = typer.Option(
False,
"--version",
"-v",
callback=version_callback,
is_eager=True,
help="Show version and exit.",
),
) -> None:
"""Render a chemical structure as ASCII/Unicode art.

Expand Down Expand Up @@ -221,7 +212,7 @@ def main(
unicode_renderer.render_molecule(mol)
else:
# Default to magic
magic_renderer = AsciiMagicRenderer(columns=columns)
magic_renderer = AsciiMagicRenderer(columns=columns, codes=escape_codes)
magic_renderer.render_molecule(mol)


Expand Down
63 changes: 63 additions & 0 deletions src/chemscii/renderers/ascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@

from chemscii.renderers.base import BaseRenderer

# ANSI color codes
_COLORS: dict[str, str] = {
"red": "\033[91m",
"blue": "\033[94m",
"yellow": "\033[93m",
"cyan": "\033[96m",
"reset": "\033[0m",
}

# Element to color mapping
_ELEMENT_COLORS: dict[str, str] = {
"O": "red",
"N": "blue",
"S": "yellow",
"F": "cyan",
"Cl": "cyan",
"Br": "cyan",
"I": "cyan",
}


class AsciiRenderer(BaseRenderer):
"""Renders chemical structures using basic ASCII characters."""
Expand All @@ -14,3 +34,46 @@ class AsciiRenderer(BaseRenderer):
_DIAG_DOWN = "\\"
_DOUBLE = "="
_TRIPLE = "#"

def __init__(
self,
width: int = -1,
height: int = -1,
padding: int = 2,
color: bool = True,
) -> None:
"""Initialize the ASCII renderer.

Args:
width: Canvas width in characters (-1 for auto).
height: Canvas height in characters (-1 for auto).
padding: Padding around the molecule in characters.
color: Whether to colorize element symbols.
"""
super().__init__(width=width, height=height, padding=padding)
self.color = color

def _draw_atom(self, canvas: list[list[str]], x: int, y: int, symbol: str) -> None:
"""Draw an atom symbol on the canvas with optional color.

Args:
canvas: The character canvas.
x: Canvas x coordinate.
y: Canvas y coordinate.
symbol: Element symbol to draw.
"""
if self.color and symbol in _ELEMENT_COLORS:
color_name = _ELEMENT_COLORS[symbol]
color_code = _COLORS[color_name]
reset_code = _COLORS["reset"]
# Apply color to the entire symbol
for i, char in enumerate(symbol[:2]):
px = x + i
if 0 <= y < self.height and 0 <= px < self.width:
canvas[y][px] = f"{color_code}{char}{reset_code}"
else:
# Default behavior without color
for i, char in enumerate(symbol[:2]):
px = x + i
if 0 <= y < self.height and 0 <= px < self.width:
canvas[y][px] = char
10 changes: 8 additions & 2 deletions src/chemscii/renderers/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ class AsciiMagicRenderer:
the image to ASCII art using the ascii_magic library.
"""

def __init__(self, columns: int = 120) -> None:
def __init__(self, columns: int = 120, codes: bool = True) -> None:
"""Initialize the renderer.

Args:
columns: Width of the ASCII art output in characters.
codes: Include escape codes.
"""
self.columns = columns
self.codes = codes

def render_molecule(self, mol: Mol) -> str:
"""Render a molecule as ASCII art.
Expand All @@ -36,7 +38,11 @@ def render_molecule(self, mol: Mol) -> str:
"""
img = self._mol_to_image(mol)
art = AsciiArt.from_pillow_image(img)
txt: str = art.to_terminal(self.columns)
if self.codes:
txt: str = art.to_ascii(self.columns)
print(repr(txt))
else:
txt = art.to_terminal(self.columns)
return txt

def _mol_to_image(
Expand Down
Loading