OmniGraffle Generator

Generate native OmniGraffle .graffle files programmatically — no macOS or OmniGraffle installation required.

Built as a Claude Skill and also usable as a standalone Python library.

What It Does

  • Generates .graffle files that open natively in OmniGraffle 7+ (macOS & iOS)
  • Supports: rectangles, circles, rounded rects, octagons, stars, text labels, colored fills, named layers, line connections, arrows, groups, rotation
  • Orthogonal (right-angle) connectors with line-hops and edge pinning — the routing real network diagrams need
  • SVG → multi-canvas converter: turn layered SVGs into one .graffle with togglable layers, every object still editable
  • Zero dependencies beyond Python 3.8+ standard library (PIL optional for preview images)
  • Three APIs: low-level builder for full control, high-level helper for quick network diagrams, CLI converter for SVG input

Quick Start

As a Python Library

pip install -e .          # from a clone; no runtime dependencies
from omnigraffle_generator import GraffleBuilder

b = GraffleBuilder(title="My Network", creator="Your Name")

server = b.add_shape(100, 100, 120, 40, text="Web Server",
                     fill_color="blue", name="web-1")
db = b.add_shape(100, 250, 120, 40, text="Database",
                 fill_color="green", name="db-1")

b.add_line(from_id=server, to_id=db, color="blue", width=2.0,
           head_arrow="FilledArrow", orthogonal=True, hop="round",
           from_side="bottom", to_side="top")

b.save("my_diagram.graffle")

Z-order gotcha: OmniGraffle paints front-first — layer 0 renders on top, the reverse of SVG. Put backgrounds and frames in the last layer, or build back-to-front and call b.save(path, z_order="back_first").

As a Claude Skill

Copy SKILL.md and the omnigraffle_generator/ directory to your Claude skill path. Claude will automatically use the skill when you ask it to generate OmniGraffle diagrams.

High-Level Network Diagram

from omnigraffle_generator.graffle_builder import create_network_diagram

builder = create_network_diagram(
    title="Data Center",
    layers=["servers", "switches", "storage"],
    nodes=[
        {"name": "Web-1", "x": 100, "y": 50, "color": "blue"},
        {"name": "Web-2", "x": 250, "y": 50, "color": "blue"},
        {"name": "LB",    "x": 175, "y": 150, "w": 140, "color": "green"},
        {"name": "DB",    "x": 175, "y": 250, "color": "red", "layer": 2},
    ],
    connections=[
        {"from_name": "Web-1", "to_name": "LB", "color": "yellow", "width": 2},
        {"from_name": "Web-2", "to_name": "LB", "color": "yellow", "width": 2},
        {"from_name": "LB", "to_name": "DB", "arrow": True, "width": 2},
    ],
)
builder.save("datacenter.graffle")

SVG → Multi-Canvas .graffle

For large layered diagrams, author one SVG per canvas and convert. SVG layer groups become togglable OmniGraffle layers; every rect/line/text stays a native editable object.

python3 omnigraffle_generator/svg_to_graffle.py rack.svg network.svg \
    --titles "1. Rack Elevation,2. Network Connectivity" \
    --output diagram.graffle

Mark connectable boxes with id="cbox:NAME" and describe connectors in a <basename>.connections.json sidecar — they become connected, orthogonal, hopping lines. Full details in docs/API.md.

API Reference

See docs/API.md for full documentation.

Available Colors

Use color names or RGB tuples (0.0-1.0):

blue red green yellow orange purple cyan pink brown white lightgray darkgray black lightblue lightgreen lightcyan lightpink lightyellow darkblue darkgreen darkred

File Format

OmniGraffle .graffle files are ZIP archives containing:

File Format Purpose
data.plist Apple Binary Plist Document model (shapes, lines, layers, styles)
preview.jpeg JPEG Thumbnail preview

This library uses Python's built-in plistlib and zipfile modules — no external tools needed.

Examples

See the examples/ directory:

  • basic_diagram.py — Simple shapes and connections
  • network_architecture.py — HPC/data center network diagram
  • multi_layer.py — Working with multiple named layers

Project Structure

omnigraffle-generator/
├── SKILL.md                 # Claude Skill definition
├── pyproject.toml           # Packaging (pip install -e .)
├── omnigraffle_generator/
│   ├── graffle_builder.py   # Core builder module
│   ├── svg_to_graffle.py    # SVG -> multi-canvas .graffle converter (CLI)
│   └── rtf_helper.py        # RTF text generation
├── examples/
│   ├── basic_diagram.py
│   ├── network_architecture.py
│   └── multi_layer.py
├── docs/
│   ├── API.md               # Full API reference
│   └── FORMAT.md            # .graffle format documentation
├── assets/
│   └── icons/               # (future) embedded icon library
└── tests/
    ├── test_builder.py
    └── test_svg_to_graffle.py

Tests

python3 tests/test_builder.py
python3 tests/test_svg_to_graffle.py
# or, with pytest installed:
python3 -m pytest tests/ -v

Roadmap

  • Core shape and line generation
  • Named layers with correct ordering
  • RTF text with fonts, sizes, colors
  • Connected lines with arrows
  • Groups
  • Rotation
  • Orthogonal connectors, line-hops, edge pinning
  • SVG → multi-canvas converter with layer preservation
  • Circle / RoundRect / Octagon / Star shapes
  • Packaging (pip install) and CI
  • Embedded PNG/SVG icons (server, switch, router, rack)
  • Gradient fills
  • Auto-layout algorithms
  • .gstencil stencil file generation

How It Was Built

This library was built by reverse-engineering the OmniGraffle 7 .graffle file format from real-world network architecture diagrams. The source diagrams are not redistributed; the findings in docs/FORMAT.md are reproducible against any comparable .graffle document. The format analysis and initial code generation were done collaboratively with Claude (Anthropic).

See docs/FORMAT.md for the complete format specification.

Security

This library only reads and writes local files. It makes no network calls, runs no subprocesses, and uses no eval/exec/pickle — output is assembled with plistlib and zipfile from the standard library.

Notes on handling input SVGs, which are the only untrusted data the tools consume:

  • XML entity declarations are rejected. Custom entities are the vehicle for entity-expansion ("billion laughs") attacks, where a file of a few hundred bytes expands to gigabytes and exhausts memory. Diagram SVGs never need them. A plain <!DOCTYPE svg PUBLIC …> with no entity declarations is still accepted for compatibility with older Illustrator/Inkscape exports; the external DTD it references is never fetched.
  • External entities are not resolved by Python's expat binding, so an input SVG cannot read local files or reach the network.
  • The converter refuses to overwrite an existing output file — bump the version or rotate first.

No identifying data is embedded in generated documents. Creator and DocumentVersionLabel default to neutral values and are set with --creator / --doc-label; a test asserts they are caller-supplied rather than hard-coded.

If you find a security issue, please open an issue.

Contributing

Contributions welcome! If you have .graffle files with features not yet supported (circles, images, gradients), opening an issue with the file attached helps us expand coverage.

License

MIT License — see LICENSE.

Credits

Created by Irfan Tamboli under the OpenAnvil organization.

Built with insights from: