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
.grafflefiles 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
.grafflewith 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
0renders on top, the reverse of SVG. Put backgrounds and frames in the last layer, or build back-to-front and callb.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 connectionsnetwork_architecture.py— HPC/data center network diagrammulti_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
-
.gstencilstencil 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:
- OmniGraffle 7 by The Omni Group
omnigraffle-stencilby Maciej Radzikowskigraffle2svg
No comments yet
Be the first to share your take.