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