Export OG object data

Hi,

Is there a simple way to export object data to excel, csv or similar?

I am not a very experience OG user and trying to figure out a way to create a list of material (BoM, bill of material) from the objects/stencils of my canvas. Would need this this for planning and documentation purposes of my audio/video/broadcast-technology engineering projects.

I would like to manage relevant data like, serial numbers, cable numbers, configuration details etc.in OG.

It would be helpful and preferable to have all information and data in one OG document without the need to create and maintain separate external lists containing the data.

Thanks for your help!

Maybe someone from the Automation section could help you with this.

I don’t see anything in the Inside OG on-line help or in the help manual.

Only reaching here but maybe exporting to OmniOutliner format would get you what you want.

1 Like

If you select objects with associated data and select Edit->Copy as->AppleScript or JavaScript you will find the object data in the text. With some “text cleaning” you will get the relevant data. So at least it’s possible to export but simple? The code you get from this export also helps you to write an AppleScript/JavaScript to automate the export.

2 Likes

Plain Edit > Copy As also puts a usefully structured representation into the com.omnigroup.OmniGraffle.GraphicType component of of the clipboard.

(In some ways a bit more useful that the slightly flat and unilluminating sequence of mutations emitted by Copy As > JavaScript)

For example from a selection like:

08

We get

Click disclosure triangle to expand com.omnigroup.OmniGraffle.GraphicType details
{
  "Layers": [
    {
      "Print": true,
      "Lock": false,
      "View": true,
      "Name": "Layer 1",
      "Artboards": false
    }
  ],
  "Origin": "{0, 0}",
  "Scale": "No scale",
  "Color": {
    "g": "1",
    "space": "srgb",
    "r": "1",
    "b": "1"
  },
  "GraphicsList": [
    {
      "Flow": "Clip",
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "ShapedGraphic",
      "Name": "Second",
      "ID": 7,
      "Style": {
        "stroke": {
          "Color": {
            "g": "0.596063",
            "archive": {},
            "r": "0.56982",
            "b": "0.745393"
          },
          "CornerRadius": 5,
          "Width": 0.5
        }
      },
      "Shape": "Circle",
      "FitText": "Clip",
      "Bounds": "{{412.5, 294.5}, {87.5, 88.5}}",
      "UserInfo": {
        "Ordinal": "2",
        "Value": "15"
      },
      "Notes": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0\\cname textColor;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Right hand circle}",
      "LayerIndex": 0,
      "Text": {
        "VerticalPad": 0,
        "TextAlongPathGlyphAnchor": "center",
        "Text": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\csgray\\c0;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\qc\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Beta}"
      }
    },
    {
      "Flow": "Clip",
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "ShapedGraphic",
      "Name": "Third",
      "ID": 6,
      "Style": {
        "stroke": {
          "Color": {
            "g": "0.596063",
            "archive": {},
            "r": "0.56982",
            "b": "0.745393"
          },
          "CornerRadius": 5,
          "Width": 0.5
        }
      },
      "Shape": "Circle",
      "FitText": "Clip",
      "Bounds": "{{190.5, 294.5}, {87.5, 88.5}}",
      "UserInfo": {
        "Ordinal": "3",
        "Value": "24"
      },
      "Notes": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0\\cname textColor;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Left hand circle}",
      "LayerIndex": 0,
      "Text": {
        "VerticalPad": 0,
        "TextAlongPathGlyphAnchor": "center",
        "Text": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\csgray\\c0;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\qc\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Gamma}"
      }
    },
    {
      "Flow": "Clip",
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "ShapedGraphic",
      "Name": "First",
      "ID": 5,
      "Style": {
        "stroke": {
          "Color": {
            "g": "0.596063",
            "archive": {},
            "r": "0.56982",
            "b": "0.745393"
          },
          "CornerRadius": 5,
          "Width": 0.5
        }
      },
      "Shape": "Circle",
      "FitText": "Clip",
      "Bounds": "{{300, 137}, {87.5, 88.5}}",
      "UserInfo": {
        "Ordinal": "1",
        "Value": "10"
      },
      "Notes": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0\\cname textColor;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Top circle}",
      "LayerIndex": 0,
      "Text": {
        "VerticalPad": 0,
        "TextAlongPathGlyphAnchor": "center",
        "Text": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1671\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\csgray\\c0;}\n\\pard\\tx560\\tx1120\\tx1680\\tx2240\\tx2800\\tx3360\\tx3920\\tx4480\\tx5040\\tx5600\\tx6160\\tx6720\\pardirnatural\\qc\\partightenfactor0\n\n\\f0\\fs24 \\cf2 Alpha}"
      }
    },
    {
      "Name": "Line",
      "Points": [
        "{234.25, 338.75}",
        "{343.75, 181.25}"
      ],
      "Tail": {
        "ID": 6
      },
      "LayerIndex": 0,
      "AllowLabelDrop": false,
      "Head": {
        "ID": 5
      },
      "Style": {
        "shadow": {
          "Draws": "NO"
        },
        "stroke": {
          "Legacy": false,
          "Color": {
            "g": "0.149131",
            "space": "srgb",
            "r": "1",
            "b": "0"
          },
          "Width": 4,
          "LineType": 1
        },
        "fill": {
          "Draws": "NO"
        }
      },
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "LineGraphic",
      "ID": 11,
      "LogicalPath": {
        "elements": [
          {
            "point": "{234.25, 338.75}",
            "element": "MOVETO"
          },
          {
            "point": "{343.75, 181.25}",
            "element": "LINETO"
          }
        ]
      }
    },
    {
      "Name": "Line",
      "Points": [
        "{456.25, 338.75}",
        "{234.25, 338.75}"
      ],
      "Tail": {
        "ID": 7
      },
      "LayerIndex": 0,
      "AllowLabelDrop": false,
      "Head": {
        "ID": 6
      },
      "Style": {
        "shadow": {
          "Draws": "NO"
        },
        "stroke": {
          "Legacy": false,
          "Color": {
            "g": "0.149131",
            "space": "srgb",
            "r": "1",
            "b": "0"
          },
          "Width": 4,
          "LineType": 1
        },
        "fill": {
          "Draws": "NO"
        }
      },
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "LineGraphic",
      "ID": 10,
      "LogicalPath": {
        "elements": [
          {
            "point": "{456.25, 338.75}",
            "element": "MOVETO"
          },
          {
            "point": "{234.25, 338.75}",
            "element": "LINETO"
          }
        ]
      }
    },
    {
      "Name": "Line",
      "Points": [
        "{343.75, 181.25}",
        "{456.25, 338.75}"
      ],
      "Tail": {
        "ID": 5
      },
      "LayerIndex": 0,
      "AllowLabelDrop": false,
      "Head": {
        "ID": 7
      },
      "Style": {
        "shadow": {
          "Draws": "NO"
        },
        "stroke": {
          "Legacy": false,
          "HeadArrow": "FilledArrow",
          "Color": {
            "g": "0.149131",
            "space": "srgb",
            "r": "1",
            "b": "0"
          },
          "Width": 4,
          "LineType": 1
        },
        "fill": {
          "Draws": "NO"
        }
      },
      "FontInfo": {
        "Size": 12,
        "Color": {
          "space": "gg22",
          "w": "0"
        },
        "Font": "Helvetica"
      },
      "Class": "LineGraphic",
      "ID": 9,
      "LogicalPath": {
        "elements": [
          {
            "point": "{343.75, 181.25}",
            "element": "MOVETO"
          },
          {
            "point": "{456.25, 338.75}",
            "element": "LINETO"
          }
        ]
      }
    }
  ],
  "ZoomLevel": 1
}
2 Likes

A Keyboard Maestro macro for copying selected graphics in the structured JSON (GraphicType) format.

Copy from OmniGraffle 7 as GraphicType JSON.kmmacros.zip (10.5 KB)

1 Like

and a JavaScript for Automation script which:

  1. Assumes that JSON of the kind shown above has been copied from an OG selection and saved to a file on the Desktop, and
  2. returns a tab-delimited line with graphic name and custom data for each shape, for example:
First    1    10
Second    2    15
Third    3    24

Rough draft of a script:

(() => {
    'use strict';

    const main = () => {
        const dctSeln = JSON.parse(
            readFile('~/Desktop/ogSelection.json')
        );

        return unlines(
            map(x => x.join('\t'))(
                sortBy(comparing(snd))(
                    dctSeln.GraphicsList.flatMap(
                        g => 'ShapedGraphic' === g.Class ? (
                            [
                                [g.Name].concat(Object.values(g.UserInfo))
                            ]
                        ) : []
                    )
                )
            )
        );
    };

    // GENERIC FUNCTIONS ----------------------------------

    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        x => y => {
            const
                a = f(x),
                b = f(y);
            return a < b ? -1 : (a > b ? 1 : 0);
        };

    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
        // The list obtained by applying f
        // to each element of xs.
        // (The image of xs under f).
        xs => (
            Array.isArray(xs) ? (
                xs
            ) : xs.split('')
        ).map(f);

    // readFile :: FilePath -> IO String
    const readFile = fp => {
        // The contents of a text file at the
        // path file fp.
        const
            e = $(),
            ns = $.NSString
            .stringWithContentsOfFileEncodingError(
                $(fp).stringByStandardizingPath,
                $.NSUTF8StringEncoding,
                e
            );
        return ObjC.unwrap(
            ns.isNil() ? (
                e.localizedDescription
            ) : ns
        );
    };

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        xs => xs.slice()
        .sort((a, b) => f(a)(b));

    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');

    // MAIN ---
    return main();
})();

2 Likes

Wow! Thanks for all the valuable and detailed response. It might need some time to figure out how to implement and test this since I am not experienced using macros, JSON etc. But it’s great to know that is a way to do this.

Thanks!

1 Like

A combined macro (Copy As > Tab-delimited Custom User Data) for copy pasting between OmniGraffle and Excel etc:

JavaScript Source
(() => {
    'use strict';

    // Convert any OmniGraffle shapes in the clipboard
    // to tab-delimited (Name +) User Data.
    // TSV lines containing User Data values
    // (as listed in the OmniGraffle Object Inspector)
    // are preceded by a TSV header with field names.

    // Rob Trew 2020
    // Ver 0.02

    ObjC.import('AppKit');

    // main :: IO ()
    const main = () =>
        either(
            msg => msg
        )(
            tsv => (
                copyText(tsv),
                'Tab-delimited user data now in clipboard.'
            )
        )(
            bindLR(
                ogGraphicTypeJSONFromPBoardLR()
            )(
                json => bindLR(
                    jsonParseLR(json)
                )(
                    dct => Right(tsvFromShapes(
                        dct.GraphicsList.filter(
                            g => 'ShapedGraphic' === g.Class
                        )
                    ))
                )
            )
        );

    //----------- TSV FROM OMNIGRAFFLE CLIPBOARD -----------

    // ogGraphicTypeJSONFromPBoardLR :: IO () ->
    // Either String String
    const ogGraphicTypeJSONFromPBoardLR = () => {
        const
            ogType = 'com.omnigroup.OmniGraffle.GraphicType',
            pBoard = $.NSPasteboard.generalPasteboard;
        return ObjC.deepUnwrap(
            pBoard.pasteboardItems.js[0].types
        ).includes(ogType) ? (
            Right(JSON.stringify(
                ObjC.deepUnwrap(
                    pBoard.propertyListForType(
                        ogType
                    )
                ),
                null, 2
            ))
        ) : Left(
            'No OmniGraffle graphic content in clipboard.'
        )
    };

    // tsvFromShapes :: [Dict] -> String
    const tsvFromShapes = shapeDicts => {
        const
            userKeys = allKeysFromRecords(
                shapeDicts.flatMap(x => {
                    const dctUser = x.UserInfo;
                    return Boolean(dctUser) ? (
                        [dctUser]
                    ) : [];
                })
            ),
            emptyRecord = userKeys.map(x => '');
        return unlines([
            'Name\t' + userKeys.join('\t'),
            ...shapeDicts.flatMap(x => {
                const
                    maybeName = x.Name,
                    maybeKVs = x.UserInfo;
                return (
                    (maybeName || maybeKVs) ? [
                        [maybeName || ''].concat(
                            maybeKVs ? (
                                userKeys.map(
                                    k => maybeKVs[k] || ''
                                )
                            ) : emptyRecord
                        ).join('\t')
                    ] : []
                );
            }).sort()
        ]);
    };

    //------------------------ JXA -------------------------

    // copyText :: String -> IO String
    const copyText = s => {
        // String copied to general pasteboard.
        const pb = $.NSPasteboard.generalPasteboard;
        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };

    //----------------- GENERIC FUNCTIONS ------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // allKeysFromRecords :: Dicts -> [String]
    const allKeysFromRecords = dicts =>
        Object.keys(
            dicts.flatMap(Object.keys)
            .reduce(
                (a, k) => Object.assign(a, {
                    [k]: true
                }), {}
            )
        ).sort();

    // bindLR (>>=) :: Either a -> 
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        fr => e => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // jsonParseLR :: String -> Either String a
    const jsonParseLR = s => {
        try {
            return Right(JSON.parse(s));
        } catch (e) {
            return Left(
                `${e.message} (line:${e.line} col:${e.column})`
            );
        }
    };

    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');

    // MAIN ---
    return main();
})();
1 Like

Thanks. This is exactly what I need. Just happened install Keyboard Meastro a couple of weeks ago and still learning what this tool is able to do.

1 Like

Hi,
Could you help me getting this to work?

I have installed the Copy As TSV User Data for OmniGraffle 7 macro into Keyboard Macro, but I keep getting the message that no Omnigraffle content is found on the clipboard, whatever I try.

I’m using OG 7.17.5, KM 9.0.6 on Mac OS 10.15.5

Seems to be working here on 7.17.5 (v203.12.0)

Presumably you are selecting shapes before using it,
and are using the Pro edition of OG (for the scripting interface)?

Here is a current copy from my KM installation:

Copy from OmniGraffle 7 as Tab-Delimited Object User Data of Shapes.kmmacros.zip (11.5 KB)

Yes I am using OG Pro

Could you give me the sequence of events for using your macro? I am relatively new to Keyboard Macro, so I have trouble debugging it at the moment.

  • The macro needs to be in a KM group which is enabled for OmniGraffle,
  • the macro itself needs to be enabled,
  • one or more OG shapes need to be selected in the OG UI,
  • OG needs to be the front application (don’t switch focus to KM),
  • the macro needs to be launched with some trigger (see the top of the KM macro)
  • (the default trigger, which you can change, is the Ctrl U (^U) keystroke).
1 Like

Thank you!
The missing step was putting the macro in the Global Macro Group.

1 Like