Translating Illustrations: JXA or OmniJS, or something else entirely?

Starting in early 2016 I have been working on and off on a toolkit (available on Github) to support me in creating and maintaining a large set of illustrations I created in OmniGraffle for Sociocracy 3.0.

However, almost three years later, I find that did not work out too well, and due to a number of technical challenges I encountered, I now need to figure out where to go from here.

So I decided to explain my problem in some detail here, including a bit of background of the project I’m using this for, what I discovered when automating OmniGraffle, and some strategies I already considered.

I’m curious for any ideas or recommendations, and especially for corrections of my many misconceptions and unfounded assumptions.

Also, maybe there is somebody out there who benefits from my experiences and research. Please contact me if you think I might be able to help you out.

And just so that you know what to expect: At roughly 5000 words, this will be a 15-20 minute read, more if you start clicking on links. All my code I am talking about in this post is available on Github.

What am I trying to achieve?

First a bit of context: I am co-author of Sociocracy 3.0 (a.k.a “S3”), a conceptual framework and a practical guide for more conscious and effective collaboration on organizations.

We release everything about S3 under CC BY SA, because we want to make it as simple as possible for people to use and remix what we provide in all sorts of ways they may find valuable. The main resource is a handbook that can be published in various formats through an open source toolkit I built on top of other free and/or open source software, but there’s a few other resources we create, too.

Adoption of S3 was slow at first, but now it’s picking up pace, and an increasing number of people want to contribute to translating resources into their own language to make them accessible to their peers and coworkers. As an open source project, we can use the translation platform Crowdin for free, and we currently have a German, a French and a Hebrew translation already online. Translations are done by the community, and thanks to automation, we can publish a new version in minutes. That’s how it should be in the 21st century, and I know that without a professional translation platform the quality of the result would be significantly lower. Thanks, Crowdin, you’re making a huge difference for us!

The case for automation

However, at the moment, all the translation magic only works for text, and not for the 130 illustrations I have created in OmniGraffle for the project.

Maintaining those illustrations in one language is a challenge in itself, because whenever we learn something new about teaching S3, we want to update our resources so that people can benefit from what we learned. That means that, as we are updating the text, there is also a lot of updates to individual illustrations, and continuous refactoring of the entire body of illustrations to maintain coherence.

Translating illustrations is still a technical nightmare these days, because there is little tool support and a lack of open standards and formats, and of course it becomes even harder when there is a steady stream of new versions of those illustrations. The volunteers, who do all the translations in their spare time, should not be bothered with menial technical tasks like copying and pasting text out of the translation platform into a proprietary Mac App. Most of them don’t even own a Mac.

Still, I believe with good software, after some transition period where I learn how illustrations need to be laid out to support multiple languages, we should be able to achieve almost full automation for building translated illustrations.

At the moment, I maintain the German translations of those illustration myself, and that is, frankly, a major pain in the ass, and a process that will not scale for any language I do not speak myself. It’s also definitely not how I think things should be in the 21st century. Now that there’s a Russian and a Dutch version coming up, I really need to get my act together and figure out how to automate this properly.

Automating OmniGraffle with Python

When I first started looking into automation, I was looking for a solution for exporting a growing number of illustrations whenever we had to build a new version of our English handbook (which was, at that time, little more like an extended slide deck created with Markdown and Deckset and a few small Python scripts).

I quickly I decided that AppleScript was not the way forward for me, mainly because I failed to see how I could use version control and automated tests with Script Editor and Automator, and the fact that the official documentation did not talk about either raised some red lights for me. (Note: I now know that this is possible, but you have to jump through a few hoops)

Then I discovered a Github project for exporting OmniGraffle documents that used Python, so I forked that and built some code on top of it until I ended up with a fairly comprehensive toolkit that could do the following:

  • export any number documents to various formats with various settings (while preserving the current export settings in OmniGraffle)
  • replace colors and fonts in OmniGraffle documents
  • run any other code on any object inside the document (because it had a simple plugin API)

Development was mostly triggered from changes I needed to make to my illustrations, e.g. when I found that I needed to update the illustrations to use a new font and a consistent color palette, I started writing some code for that.

And when we decided to translate our handbook into other languages, I started looking into ways supporting that with software as well.

We ended up using Crowdin as a translation platform for the S3 handbook, because that’s free for OpenSource projects. I fully automated the build process, and I intended to do the same for extracting texts and injecting translations into OmniGraffle documents. Can’t be too hard, I thought. But I discovered more and more problems.

While exporting files had been pretty straightforward, interacting with objects in the document was not so simple. At first I thought this was just because documentation was limited to the dictionary that is visible in Script Editor, and the technology to connect Python to Omnigraffle - py-appscript - had been unmaintained for a couple of years.

Here’s a couple of the problems I found (and reported to OmniGroup support):

  1. Read access to some elements inside a document is broken, most notably for everything inside shared layers, but also for some (but not all) groups, subgraphs, tables and solids
  2. Writing properties (e.g changing text) and then saving the file often results in OmniGraffle not saving those changes. I must say this was a really surreal experience: I watched OmniGraffle replace the text with a translation on my screen, and afterwards the saved file still contained the original text afterwards. I found a workaround: when adding some User Data to the object where I updated text, the object saved just fine.
  3. Some values were not exposed by OmniGraffle, e.g. the opacity, which I relied on in my illustrations

Support acknowledged those issues, but for all I know they were never fixed, at least they never told me that it was.

Then there was a new version of OmniGraffle (7.8?) with change in export API that broke my code, so that I decided to stick with the older version until I could find some time to figure out how to move forward.

I had some workarounds in place, disabling shared layers before extracting and inserting, and a lot of manual steps, so for a while, I was able to live with the situation.

Automating OmniGraffle with JXA

Things changed when 2 months ago I finally decided get a new SSD for my Macbook and go for a new installation of High Sierra, because after that I somehow was unable to get py-appscript to work on my machine. After a bit of fruitless tinkering and researching, I decided to dive into JXA, because I thought this might be a more sustainable approach. My JavaScript was a little rusty, but I thought with today’s toolchain (and all the code available through npm) it should be pretty straightforward to port my Python code to JXA. I discovered the JXA Cookbook, which helped me get started, and for my first tests I created a Service with automator for exporting OmniGraffle documents.

As my code got a bit more complex, I settled for running JavaScript through osascript in the terminal, because that way I am able to use Sublime Text with ESlint, and the Safari debugger. With that approach, however, it’s impossible to split code into several files without sacrificing line numbers when an error is thrown. I learned that node-jxa might be a way forward here, and I will definitely try that should I decide to stick with JXA.

The current state of my experiments with JXA and Omnigraffle is available on Github.

Here’s a list of the problems I found with JXA

  1. Export is broken, the exported files do not contain all visible objects (it’s possible that this bug only affects objects in shared layers, but I’m not sure)
  2. Some objects in the document can’t be accessed through JXA, again it’s all items in shared layers, but also some groups, tables and subgraphs
  3. Sandboxing sometimes gets in the way of scripting, because OmniGraffle is not allowed to access documents. So I needs to manually open all files before running a script, which is really annoying.

I raised these issues with OmniGroup support, and I hope they will be fixed soon. That won’t fix the fundamental flaws in JXA, but might make for a workable solution if node-jxa works ok.

What about OmniJS?

Apparently there’s two different JavaScript APIs available for OmniGraffle: the API Reference (available inside OmniGraffle) has groups represented as simply another Graphic in a Layer’s Graphics collection, and the OmniGraffle dictionary (available in Script Editor) lists groups as a separate collection within a layer. From now on, I will refer to what is described in the API reference inside OmniGraffle as OmniJS.

Even with OmniJS, the problems with shared layers are still present, you can see for yourself by opening this test document, and then the console (in v7.10.2):

> canvases[0].layers[0].name
> Layer 1
> canvases[0].layers[1].name
> shared
> canvases[0].layers[0].graphics.length
> 2
> canvases[0].layers[1].graphics.length
> 2
> canvases[0].layers[0].graphics[1]
> [object Shape]
> canvases[0].layers[1].graphics[1]
> undefined
> canvases[0].layers[1].graphics[0]
> Error: Attempted to return result with invalid instance of Shape: Error: Model removed from portfolio

(Note: It’s a pity the console does not allow for copy and paste of several statements and the corresponding output.)

Edit: revisiting this problem showed that the bug is a bit more complex to trigger, and I appear to have missed a line in my original post when I copied them individually for the console into this post)

Fun fact: When I attempt to copy any object in a shared layer as JavaScript through the context menu, the resulting JS code for those objects is incomplete, because obviously even OmniGraffle cannot access them.

Also, when I get some text that is formatted, this formatting is lost.

> canvases[2].layers[1.graphics[3].text
> Box with some bold and some italic text.
> And a second paragraph

This last bit alone means that I will not be able to automate translations through OmniJS. Rich Text exposed as attribute runs in the other document model may be a pain to process in JavaScript, but as long as I’d stick to very basic formatting, it’s possible to convert that into Markdown (or similar) and back. The new text property, however, just acts as if the formatting isn’t there. Or maybe am I missing anything obvious here?

I also had a closer look at omni-automation.com, which appears to be the most comprehensive documentation apart from the API Reference itself, and I downloaded the example plugin. I learned a lot, and left with a few unanswered questions:

  1. Can I use the safari debugger for debugging plugins? Would that work with code in libraries also?
  2. Would I need to open/close omnigraffle whenever I change code in a plugin, or would it autodetect new or changed actions ?
  3. For my toolkit to process a number of OmniGraffle documents, it appears I would require a plugin, and trigger actions inside the plugin through Script Links (from a command line script or through a service), is that correct?
  4. What is the best setup for developing and testing plugins? I can imagine a symlink inside the OmniGraffle plugins folder that points to a working copy, I could also clone the repo directly to the plugins folder (which would impose a certain structure on the repo), or I could “publish” to the plugins folder (e.g. with a makefile) whenever I change some code.
  5. is there a specific coding style encouraged for plugins (the example code is a bit inconclusive here)?

Personally, I find the setup costs for writing an OmniGraffle plugin is rather high, but maybe that’s typical for plugins developed in JavaScript? Here is an example where you can create a plugin with a mere 4 lines of code in a single file, and here is another one where a plugin needs only a single line of boilerplate code.

Where to go from here?

On top of the Hebrew and French translations, which are in dire need of illustrations in each language, there’s also Russian, Spanish and Dutch translations projects on their way now. So I need to come up with a solution soon.

I found four general approaches, which are not necessarily mutually exclusive:

  1. Forget about extracting from and inserting texts into OmniGraffle, and support manual translation of assets as good as I can.
  2. Wait until the bugs in OmniGraffle are fixed, and deal with the technical idiosyncrasies of automation through JXA.
  3. Translate OmniGraffle source files.
  4. Move my illustrations out of OmniGraffle to another platform.

Maybe there’s even something else I haven’t yet thought about?

Let’s look at each one in a bit more detail:

Strategy 1: No automation

No automation basically means translations have to be manually inserted into standard image files (PNG or SVG), because I cannot expect volunteers to own an OmniGraffle license, let alone a Mac. There’s many free SVG editors and image editors for any platform, some are even browser-based, so the barrier of entry might be quite low.

Crowdin provides some support for translating assets , they can be included in the proofreading workflow, but will not benefit from auto-translation and frictionless collaboration between multiple translators. There’s a two things I could do to patch this up and assist translators:

  • OmniJS’s ability to export plain text might be used to put source texts into crowdin, so that translations benefitting from all features of the translation platform can then be copied and pasted into the image editor and formatted manually.
  • Automatically create and upload image-diffs to Crowdin, so that translators can see the actual changes to an illustration and decide whether or not this warrants a new version.

But all that still feels like sticking a band-aid to an amputation.

For SVG files, it first appeared that I would merely need to figure out how to integrate them in to my publishing workflow, and everything else should be a piece of cake. A closer look revealed that while text in SVG exported from Omnigraffle is visually quite similar to the source, the structure of the text is structure is broken up so badly it will be a nightmare to edit in Inkscape.

Consider this bit of Rich Text in the OmniGraffle document:

<string>{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0

\f0\fs32 \cf0 Box with some 
\b bold
\b0  and some 
\i italic
\i0  text.\
And a second paragraph}</string>

This is what it looks like when exported to SVG:

<text transform="translate(346 188.59595)" fill="black">
         <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="7.864" y="16">Box with some </tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-weight="700" fill="black" y="16">bold</tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" y="16"> </tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.128" y="34.46411">and some </tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-style="italic" font-weight="400" fill="black" y="34.46411">italic</tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" y="34.46411"> text.</tspan>
          <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="30.504" y="52.91211">And a second </tspan>
         <tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="43.256" y="71.36011">paragraph</tspan>
</text>

It’s obvious that with the absolute positioning of parts of a sentence, all attempts at translation are inevitably bound to fail, because they will end up all over the place when translated strings have a different length.

PNG files are a much bigger hassle initially, because I would need to provide translators with raw files without any text. This requires a massive refactoring of all illustrations and results in my OmniGraffle documents becoming unnecessarily complicated, moving all text out of shapes and into separate layers. Obviously that’s bad for future maintenance. It’s a pity that artboards can’t help here, because they can only export what is above them, and text is obviously above everything else. Also I would have to resort to tedious manual export with all text layers disabled, but until the export bug is fixed in OmniGraffle, that’s the only way. Once that bug is gone, a simple script could disable those layers and export everything.

But with either format, SVG and PNG, this strategy would insert a massive amount of friction into the entire translation process: the burden of maintaining quality is put entirely on the shoulders of translators, and even small updates might come with an unreasonable amount of additional work. I am concerned this might result in outdated illustrations in target languages, a sacrifice of visual quality, and definitely a significant workload dumped on top of volunteers.

But at least it’s something to try if all else fails. Translators will not like it, so maybe they would just translate what they consider important. Still, better than nothing.

However, I have a couple of OmniGraffle files with a lot more text here and here, that also need translating, but would not at all work with this approach.

Strategy 2: Stick with OmniGraffle automation

A reported all the bugs I know to the OmniGroup, and I’m sure they will eventually resolve them, however they still have their own list of priorities which need not be aligned with mine.

But even if those bugs are fixed, OmniJS would still not provide an API to extract and insert formatted text, so I assume I am still stuck with JXA, a technology Apple might kill off rather sooner than later.

Anyway, once I can get my hands on a version of OmniGraffle that fixes the bugs I reported, it probably makes sense to look into node-jxa and see if that is a way forward for me, because that would allow for standing on the shoulder of giants when it comes to existing packages and development tools. OmniJS has, at least as far as I understand it, significantly a longer way to go here.

Strategy 3: Translate Omnigraffle Source files

Three years ago, one of the first things I tried with Crowdin was uploading the OmniGraffle XML (plist) documents. Since the text inside the XML being stored as stored as Rich Text, it was not possible to extract translatable strings through the XML tools provided by Crowdin. This is why I looked at ways to extract text via the scripting bridge.

However, I guess it’s not too hard to write some code that extracts RTF from those XML files and converts it to Markdown, or any other suitable format that Crowdin understands.

There’s all kinds of potential technical pitfalls involved, but at least I’m mostly independent from the development of OmniGraffle (apart from revisions to the file format itself), and I’m free to use the full scale of tools, languages and libraries available. And I probably can start with processing only a subset of rich text. Of course, my habit of consistently using Open Sans Light for normal text and Open Sans Regular for bold text complicates things a bit, but that is not a big issue.

So that strategy actually sounds pretty promising right now.

Strategy 4: Abandoning OmniGraffle

Fully aware that the moment I migrate my illustrations out of OmniGraffle I will lose any meta information that does not have a visual representation, e.g. artboards, stencils, actions, multiple canvases in one document, shared layers, and the minimum of semantic markup for text that is present in Rich Text but not exported to SVG.

Of course, what now serves as a deterrent to leaving the platform would also become a significant barrier to returning at a later date, e.g. once all the bugs are fixed, and automation might again become a more viable option.

In my experiments with exporting SVG explained above I also tried importing the very same SVG file back into Omnigraffle. Here’s the result:

<string>{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\csgray\c0;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0

\f0\fs32 \cf2 Box with some bold\
and some italic text.\
And a second \
paragraph}</string>

The formatting of the text is simply lost. Other things are lost too, and the result looks quite different from the original. The same is true for Visio, where the result is arguably even worse. It appears once I’m out of OmniGraffle, there is probably no way back without redrawing everything.

However, the important question for me right now is: Will it help my project if I abandon OmniGraffle, and if so, where could I turn to?

There’s two directions I could take:

  1. manually re-creating all my illustrations in a language for vector graphics (or similar)
  2. using another editor with better support for translations, hopefully re-using at least some bits of my illustrations

Lets look at each in a idea more detail.

Languages for vector graphics

The illustrations for the handbook are mostly not very complicated (with the exception of some illustrations for structural patterns), and based on just a few recurring patterns. The visual style is also pretty basic, so I assume there’s a wide range of tools suitable for recreating my illustrations.

And maybe in the process I could address two things I really miss in OmniGraffle:

  • A simple way to reuse design elements and visual appearance so that whenever I change a recurring design element, it changes in all the documents it’s used. That would make refactoring illustrations so much simpler.
  • A text-based format that lends itself for meaningful diffs for each update. This implies, among other things, separation of content from visual representation.

There’s a couple of interesting approaches, most of which are collected in this blogpostand this thread on reddit. I haven’t looked too deep into this so I can only paint with a really broad brush here:

First there’s tools for Tex, mostly Pstricks, Asymptote, and TikZ/PGF . They achieve decent separation of content and style, I can imagine that a template engine or Gettext might be used for extracting and/or substituting translations. The examples available on the web look promising, although the learning curve of any of the three appears to be rather steep for somebody with only a modicum of experience with Tex. That definitely includes me (even though rendering of the PDF version of our handbook is based on Tex), and also most people who would want to use and adapt the source files of my illustrations. I might still export them to SVG, though. I would need to do some more research into this, but for now this looks like a lot of work.

Then there’s a few other languages, which — at least from what I gathered — are intended for visualization of either datasets or mathematical functions, or they lacking features and/or a significant user base. However, I’m curious for any hint that makes me re-evaluate this assumption.

Last, there’s a bunch of libraries for programming languages, e.g. for Python or Javascript, which are more accessible for me than a Tex-based solution, but again, I need more research to figure out which are the most viable options.

I think with this last approach there might even be a way to convert the output of OmniGraffle’s “Copy as JavaScript” to get me started.

Whichever technology I’d chose, I estimate a full migration of all illustrations will take me at least month.

Other editors

From my limited understanding of vector graphics formats, SVG is the most widely supported open format out there, so that’s the first thing to look at.

From what I understand it might be necessary to stick with one specific editor, because there’s different versions of SVG and certain idioms or structures one editor uses that might not survive a round trip.

Inkscape

There’s a variety of editors available, and the most comprehensive editor working with SVG as a native format is probably Inkscape. (Note that Adobe Illustrator does not write standard SVG, but embeds its proprietary AI format within the SVG files). Inkscape can be automated with Python and Ruby, which I both prefer to JavaScrip.

I’m not too happy with the Mac version of Inkscape, even though I can see a certain benefit of working with an open source app that is accessible to anyone who wants to reuse and translate my illustrations.

Since there’s obviously a bit of a learning curve, I estimate a migration of all my illustrations to Inkskape/SVG will take at least 3-4 weeks, most likely longer.

So what’s the benefit?

My first tests with Inkscape revealed that the simple example text I used above it will be directly translatable through Crowdin’s XML support. However, I don’t expect this to hold up in real life, i.e. for longer text with more formatting.

I researched a few other approaches for translating SVG files, e.g. adding HTML to SVG as foreign object, inserting translations through a template engine or using XSLT to create XLIFF from SVG, but I’m afraid each of those has only a very limited use cases, and/or is not directly supported by SVG editors. So I’m afraid, after all I wouldn’t gain much for my significant investment that makes this strategy worthwhile.

Sketch

Another avenue of further research might be Sketch, which is at least on par with OmniGraffle when it comes to usability, and has a few interesting ideas that would make my life much easier, like symbols, text styles and libraries. Sketch is extensible through JavaScript plugins and fully embraces npm, no wonder there’s already an active developer community providing 634 (!) plugins with really interesting ideas, e.g. Sketch Runner. Among those are at least seven plugins for translating Sketch files (1, 2, 3, 4, 5, 6 and 7). The fact that there’s a large community of people who already have given this matter a some thought is encouraging, even though I’d need to research a bit more to see where this might be going.

Migration of my illustrations would happen through SVG, and I’d probably take around 2-3 weeks to polish everything, because I could employ symbols, libraries and text styles to speed up the process. But maybe I’m overly optimistic, and this time I’d look closely before I leap. Especially because Sketch is again a proprietary platform on Mac, so reuse of my illustrations will still be limited.

What now?

With translation projects for at least four languages lacking access to illustrations, I need to come up with a solution rather sooner than later.

Maybe the way forward lies in putting several of those pieces together?

So I’m curious for any thoughts or wild ideas even remotely related to this challenge.

  • Can I use the safari debugger for debugging plugins? Would that work with code in libraries also?

The console in OmniGraffle Pro is going to be more helpful in debugging plugins than Safari would be, because Safari is going to accept a broader range of JavaScript than what is provided in the JavaScript scripting library in OmniGraffle. I use a combination of VSCode, and the Console in OmniGraffle when debugging scripts.

  • Would I need to open/close omnigraffle whenever I change code in a plugin, or would it autodetect new or changed actions ?

If you are using a plug-in, it will be versioned. Please increment the version inside of the plug-in. Because a plug-in loads, you should quit and relaunch when Plug-ins are changed. When you change code inside a script that isn’t inside a plugin, or a script that is a stand alone item in the Plug-Ins folder, you don’t need to quit and relaunch. Once you save the changes to the plug-ins folder, the new changes should be available.

  • For my toolkit to process a number of OmniGraffle documents, it appears I would require a plugin, and trigger actions inside the plugin through Script Links (from a command line script or through a service), is that correct?

You can process a number of documents, but currently they need to be open for you to access them. That is true if you use a plug-in or if you use a script that isn’t in a plug-in. If you want a menu of actions to call from inside OmniGraffle, you will need a plug-in. If you just want 1 item to show at the top level of your Automation menu, you can create an omnijs file that isn’t a plug-in as a standalone script. It depends what you need. A script that ends with .omnijs is an action that isn’t a plug-in. A script that has a manifest, .string files, and multiple actions with individual titles that ends in .omnigrafflejs is a plug-in.

  • What is the best setup for developing and testing plugins? I can imagine a symlink inside the OmniGraffle plugins folder that points to a working copy, I could also clone the repo directly to the plugins folder (which would impose a certain structure on the repo), or I could “publish” to the plugins folder (e.g. with a makefile) whenever I change some code.

We don’t make any specific recommendations. Whichever way works best for you.

  • Is there a specific coding style encouraged for plugins (the example code is a bit inconclusive here)?

We provide a template for creating Plug-Ins. Otherwise, I’m not aware of any specific suggestions.

For your illustrations, have you considered using variables and changing the object names instead of manipulating strings within in illustration? What I mean by that is you can use <%GraphicName%> on an object in an illustration, and then you can change the object name to the new language for that version, and without having to get and set that string, you can change the name.

Instead of using Automation to standardize your multiple language illustrations, have you considered enforcing the template style and using the power of templates and diagram styles instead of relying only on scripts alone? I don’t understand the problem you are running in to with your illustrations. You should be able to replace the text in illustrations. Can you explain what is happening within the illustrations and post an example?

Thanks,

Lanette

Thanks for your reply. I going to reply in two posts, this one about your suggestions how to move forward with translations, and another one later about plugin development.

I took the liberty to switching the order of your paragraphs around, because after the clarification you requested, probably my answers to your suggestions make more sense.

I don’t understand the problem you are running in to with your illustrations. You should be able to replace the text in illustrations. Can you explain what is happening within the illustrations and post an example?

Yeah, I was confused too with all that translation stuff in the beginning, it’s quite complex. You can find all the links to the illustrations, source texts, build tools etc. in my post above if you’re curious, but maybe it’s helpful if you look at this and this before I’ll try to explain:

I have 130 illustrations in English. I want to translate those to German, Russian, Hebrew, French and Dutch. I have also a handbook with around 150 pages, where these illustrations are used. The handbook is also translated into those languages. We use a translation platform that facilitates collaboration of several people towards the translations. Most are volunteers, nobody but has an OmniGraffle license.

Here’s the first challenge: How can I get the texts in my OmniGraffle document into the translation platform? How do I get translations back into OmniGraffle afterwards? Remember, 130 English translations, 650 translated illustrations. Probably 500 - 1000 texts per language.

But that’s not all: the handbook is continuously updated, as are the illustration. We publish snapshots a couple of times a year, and maintenance updates in between.

Here’s the second challenge: How do I track which texts in the illustrations have changed, and into which place in which illustration a newly translated text would go? With automation, that is still challenging, but will work consistently and flawlessly (apart from the occasional bug).

For your illustrations, have you considered using variables and changing the object names instead of manipulating strings within in illustration? What I mean by that is you can use <%GraphicName%> on an object in an illustration, and then you can change the object name to the new language for that version, and without having to get and set that string, you can change the name.

You are referring to data variables . That’s interesting, that would work for any text without any markup change, i.e. for probably 80-90% of the cases. However, that simply shifts the problem from accessing text to accessing user data (or object names), and accessing shared layers through JXA or OmniJS would still be required, right?
It would also make creating the illustrations a bit more complicated, as I have to edit all text through the object inspector. Some texts are a paragraph or more.

Instead of using Automation to standardize your multiple language illustrations, have you considered enforcing the template style and using the power of templates and diagram styles instead of relying only on scripts alone?

How do you “enforce” the template style? I found nothing about that in the manual. Can you elaborate a bit when in the translation process I would do that, and what that would accomplish ?

It seems like I wasn’t as specific as I’d intended. For me to isolate and fix what is going wrong with the illustrations text replacement, it would be helpful to have the smallest possible example that causes the issue. Chances of me figuring it out when looking at everything at once is low. Let’s work together and try to isolate and fix the issues one at a time to give us the best chance of success.

In terms of tracking which items have been changed, our graphics keep their same index (Graphic ID) across sessions. This means that when you refer to a graphic index of 4, it is the same graphic index even after you save, close, and reopen the document. I have a script to share with you that I used for testing this which numbers each graphic, uses the <%GraphicName%> and then checks the graphic index matches the graphic name. There are a few cases where a graphic will change index, like if you move it to another canvas.

You would not need to edit object names through the object inspector, as you can change the object name directly through the OmniGraffle JavaScript support based on the current graphic name, or Graphic ID (index). See https://www.dropbox.com/s/66p96egun3k1p71/graphic-id-checks.omnijs?dl=0 and https://www.dropbox.com/s/g4l8ubjme3ksqjl/rename-graphics-with-ids.omnijs?dl=0 to try these scripts and get a feeling for how you can change names of objects.

Diagram styles are specific to documents that use Automatic Layout. Read about it at https://support.omnigroup.com/documentation/omnigraffle/mac/7.10/en/using-templates-and-diagram-styles/. It will work only if you want your text to have the same styling applied in the destination file regardless of what it was marked up as in the incoming text. If that isn’t what you want, diagram styles won’t be helpful.

If keeping the incoming formatting is more important, it might be worth trying copy/paste from another source to keep the local style overrides on the incoming text. Part of finding the right solution may involve experimenting to find what works manually, and then codifying that solution.

I look forward to seeing your smaller example and I’ll try to find a solution in the isolated case. Then we can try to insert that into the larger script and see if it solves all issues, or just some cases.

Thanks,

Lanette

It seems like I wasn’t as specific as I’d intended. For me to isolate and fix what is going wrong with the illustrations text replacement, it would be helpful to have the smallest possible example that causes the issue. Chances of me figuring it out when looking at everything at once is low. Let’s work together and try to isolate and fix the issues one at a time to give us the best chance of success.

No worries, it appears I also wasn’t as specific as I’d intended. I’m ok with writing JavaScript code, even though that is not my favorite language, I have quite enough industry experience as a software engineer to get around that. I did express concerns whether OmniJS is the right technology, because of Sandboxing, and because it does not yet provide all features I thought I’d need (rich text), and regarding the future of JXA, because that is really a half-assed effort on Apple’s part which appears to be not very popular with JavaScript developers.

But what is blocking my progress is actually bugs in OmniGraffle, which I reported as:

  • OG #2173060 “Problems with JXA accessing items in OmniGraffle documents” (sent about a week ago, nothing but an auto-reply so far)
  • OG #2166190 “Omnigraffle: Export and JXA” (about a week ago) (which is very likely related to a problem I reported as OG #1820090 “OmniGraffle document model” (August 2017)

Today I played with setting texts and attribute runs through JXA, which had been broken in Apple Script in a previous version (I reported this as OG #1869199 in November 2017), but apparently that has been fixed, it’s working fine now. I wasn’t aware of that, because nobody told me.

My JXA code and my test document is available at https://github.com/bboc/omnigraffle-tools, (see develop and the version I pinned in a feature branch). Theres a more detailed description and a log in OG #1869199, but if you wish I can copy and paste that here.

If you need additional information or clarification, I’ll be glad to help you out.

In terms of tracking which items have been changed, our graphics keep their same index (Graphic ID) across sessions. This means that when you refer to a graphic index of 4, it is the same graphic index even after you save, close, and reopen the document. I have a script to share with you that I used for testing this which numbers each graphic, uses the <%GraphicName%> and then checks the graphic index matches the graphic name. There are a few cases where a graphic will change index, like if you move it to another canvas.

I’ll look into that, as an ID appears to be the only property that is not copied with an object. I take it IDs are unique to the document, not just the canvas?

You would not need to edit object names through the object inspector, as you can change the object name directly through the OmniGraffle JavaScript support based on the current graphic name, or Graphic ID (index). See Dropbox and Dropbox to try these scripts and get a feeling for how you can change names of objects.

Thanks for the scripts, it appears that names are accessed just like any other property in OmniJS, am I missing anything?

Also, it appears I don’t understand your suggestion. What I got was this: I add the variable <%object name%> into the text fields of each object that has text. The name variable defaults to the type of object. So if I wanted to see anything else than boxes which all say “Rectangle” I would somehow need to get the appropriate English text in, wouldn’t I? That text would then go into the name field in the object inspector. Otherwise I’d have to add text somewhere else, link it to the object id, which I’d have to find out first (how?), and then run it through a plugin that updates the name. That is not a nice workflow for creating an illustration, is it. What am I missing here?

Diagram styles are specific to documents that use Automatic Layout. Read about it at OmniGraffle 7.10 Reference Manual for macOS - Using Templates and Diagram Styles. It will work only if you want your text to have the same styling applied in the destination file regardless of what it was marked up as in the incoming text. If that isn’t what you want, diagram styles won’t be helpful.

My illustrations are not Diagrams, and thus do not use Automatic Layout.

If keeping the incoming formatting is more important, it might be worth trying copy/paste from another source to keep the local style overrides on the incoming text. Part of finding the right solution may involve experimenting to find what works manually, and then codifying that solution.

I’m afraid that is a misconception, I don’t use copy and paste. I want to create the English version of my illustrations using OmniGraffle as a WYSIWYG editor with the full feature set, styling text as I see fit. That English text is then extracted via automation and uploaded to the translation platform. After translation is complete, I trigger the build process, which includes automated downloaded the translations platform, creating a copy of the OmniGraffle document for each language, then injecting all the translations and then exporting and puts it back into a copy of the original file, which and then is exported, again through automation.

I look forward to seeing your smaller example and I’ll try to find a solution in the isolated case. Then we can try to insert that into the larger script and see if it solves all issues, or just some cases.

Thanks for helping out.

Now that I found some time to test and research things. here’s the second part of my reply to your initial response, about the workflow of developing and debugging plugins for OmniGraffle.

Can I use the safari debugger for debugging plugins? Would that work with code in libraries also?

The console in OmniGraffle Pro is going to be more helpful in debugging plugins than Safari would be,

Can you clarify that for me? I’m afraid I’m getting this wrong. All I know about the console is what’s on this page, and I compared that to the Safari debugger and the Safari console. What am I missing?

I built a simple plugin (see “debugger.omnigrafflejs”) that includes a debugger statement, and indeed it does not connect to the Web Inspector.

because Safari is going to accept a broader range of JavaScript than what is provided in the JavaScript scripting library in OmniGraffle.

Does that mean OmniJS brings its own JavaScript engine? As far as I know, JXA runs on the same JavaScript Core that comes with Safari on the OS, hence the differences in the core language in different versions of MacOS (see e.g. here), and the ability to use the Safari Web Inspector for debugging any Application’s JavaScript Context. I have used the Web Inspector successfully with scripting OmniGraffle through JXA, and also with Taskpaper extensions.

I use a combination of VSCode, and the Console in OmniGraffle when debugging scripts.

So you’re connecting a debugger to VSCode? Which debugger extension are you using? And does it also work with Libraries inside plugins?

Would I need to open/close omnigraffle whenever I change code in a plugin, or would it autodetect new or changed actions ?

If you are using a plug-in, it will be versioned. Please increment the version inside of the plug-in. Because a plug-in loads, you should quit and relaunch when Plug-ins are changed.

That is a bit weird, because when I install a plugin, I don’t have to relaunch OmniGraffle, it’s available right away. When I update code in the plugin (by editing files in the plugin folder) while OmniGraffle iOS running, and then trigger the action from the Menu, the updated code is executed.

Also, I can edit the manifest and add another action, and then add another JS file with that action, and the menu is updated in the background to show both actions.

Isn’t that the exact opposite from you described? Am I missing something here?

When you change code inside a script that isn’t inside a plugin, or a script that is a stand alone item in the Plug-Ins folder, you don’t need to quit and relaunch. Once you save the changes to the plug-ins folder, the new changes should be available.

It seems so.

For my toolkit to process a number of OmniGraffle documents, it appears I would require a plugin, and trigger actions inside the plugin through Script Links (from a command line script or through a service), is that correct?

You can process a number of documents, but currently they need to be open for you to access them. That is true if you use a plug-in or if you use a script that isn’t in a plug-in. If you want a menu of actions to call from inside OmniGraffle, you will need a plug-in. If you just want 1 item to show at the top level of your Automation menu, you can create an omnijs file that isn’t a plug-in as a standalone script. It depends what you need. A script that ends with .omnijs is an action that isn’t a plug-in. A script that has a manifest, .string files, and multiple actions with individual titles that ends in .omnigrafflejs is a plug-in.

Is you refer to as “script” is what omni-automation.com calls a “solitary action” (I believe it would go a long way if OmniGroup chose to align on terminology and provide a decent set of searchable documentation. I may be a bit thick, but I definitely have a hard time with all this).

However, I think for my case a bundle makes more sense, because:

  • OmniGraffle compiles the script as soon as it touches it, and afterwards its a binary and I can’t edit it in Sublime Text any more. Xcode opens it, but I don’t want to use Xcode for JavaScript
  • I definitely need libraries to structure my code.
  • I want git, and there’s no use in setting up a git repo directly in the plugins folder if I intend to have other scripts or plugins in there.

The API reference says it’s possible to open files with Application.openDocument(), so I can actually encode a script via Script Links and make that open a document and then run some code in a plugin. God knows how to debug an URL encoded script. But maybe I can execute a plugin via Script Link before a document is loaded, which makes the encoded script very small.

That approach, however, sounds ugly as hell, and maybe on top of that Sandboxing will get in the way of opening files, and writing extracted texts to disk.

What is the best setup for developing and testing plugins? I can imagine a symlink inside the OmniGraffle plugins folder that points to a working copy, I could also clone the repo directly to the plugins folder (which would impose a certain structure on the repo), or I could “publish” to the plugins folder (e.g. with a makefile) whenever I change some code.

We don’t make any specific recommendations. Whichever way works best for you.

Ok, after some testing, I can say that symlinks are not working for me, even when I installed the plugin manually and then replaced it with a symlink while OmniGraffle was closed. I understood you indicated that this is possible. What am I doing wrong? I tried dragging the plugin with Alt+CMD in the Finder, and also ln -s ... via the console.

Is this maybe related to Sandboxing?

Is there a way to install a plugin without manual interaction (i.e. clicking on the “Replace Plugin” button)? Because all that clicking gets old pretty fast.

Also, I can edit the manifest and add another action, and then add another JS file with that action, and the menu is updated in the background to show both actions.

In many cases it doesn’t matter. The only exception would be if you’d declared variables, gotten the view index, or any other number of commands inside a script (or plug-in, or action) that aren’t refreshed until you quit and launch again. That’s the only reason we suggest you restart. It’s up to you to determine if that is relevant. I didn’t literally mean that you are required to restart the application in every case. It’s a good troubleshooting step though.

Is there a way to install a plugin without manual interaction (i.e. clicking on the “Replace Plugin” button)? Because all that clicking gets old pretty fast.

No. Because of security, it requires a manual confirmation with a user interaction. Sorry that it’s annoying, but it’s required.

Does that mean OmniJS brings its own JavaScript engine?

Sort of. Use the Script Editor to open OmniGraffle for JavaScript you will see what is part of the JavaScript library that is provided by OmniGraffle. Does that make sense? Since you are executing the script via OmniGraffle, the console from Automation>Console may have some data that Safari debugging tools do not know about, because you aren’t running through the web browser. You are running through OmniGraffle, the application. You are expecting a level of sophistication and tooling that may not exist yet from what I’ve seen. Hopefully I am wrong and I simply haven’t seen the right combination yet, but I haven’t seen debugging tools that understand OmniGraffle specific JavaScript. Our JavaScript support was just added this past year and hasn’t made it to all Omni applications. You are pushing the boundaries of what is possible, and I don’t think what you are trying to do with retaining text attributes (without even pasting) is even possible with the current capabilities, unless JXA offers something that OmniGraffle’s JavaScript library doesn’t offer.

So you’re connecting a debugger to VSCode? Which [debugger extension]

No. I’m debugging literally by reading what is in the console, considering it using my brain and no tooling, and by isolating things into small parts so that I can simplify my scripts when something goes wrong. There is not advanced tooling currently for using the JavaScript inside of OmniGraffle that I know of. If you get something working, I’d be interested in what you used.

Is you refer to as “script” is what omni-automation.com calls a “solitary action” (I believe it would go a long way if OmniGroup chose to align on terminology and provide a decent set of searchable documentation. I may be a bit thick, but I definitely have a hard time with all this).’

I think this is covered on the page that you referred to. It is called both depending on where and if you are running it or calling it. The script is code contained in a single file intended to run in OmniGraffle. It may or may not be an action that is saved on disk in the plug-ins folder. It becomes an action when you save it and call it. It is a script when you run it from within the console. A script can become an action, or become part of a plug-in. It isn’t my goal to recreate the documentation here in the forums, or to speak for all of The Omni Group. I’m just trying to help you get your script working.

But maybe I can execute a plugin via Script Link before a document is loaded, which makes the encoded script very small.

I wouldn’t expect that to work. You need the document open to access the contents within it and work with it in many cases. You need OmniGraffle running and the documents open if you want to act on them. This may not always be the case, but I believe it is required for reliable results.

While there are many things you can do with our JavaScript implementation, there are also limitations, and I don’t think OmniGraffle is unique in that. You may want to try a few other ways of implementing your ideas. You could find something that works better, or you might find a different set of problems. Either way, it would be educational to hear about what you’ve learned.

Lots to consider here with your other post, but I hope at least I’ve helped you understand where the tooling is at and how the OmniGraffle scripting library for JavaScript and JXA are different.

Thanks,
Lanette

Lanette,

thanks a lot for all the support you provided. Decided to send in a bug report about the debugger, because obviously it works for other Mac Apps that use JavaScriptCore for their plugins.

Also I am quite sad there has been zero communication form OmniGraffle support in the last two weeks about the bugs and crashes I filed. Automation being a professional feature, I kinda assumed professional support, which typically includes some kind of timeframe when issues will be resolved.

Thanks for sending in reports. I see your crash report, but I am unable to reproduce it here. Unless we have a fix or a workaround, we track crash reports, but do not respond to them by default. Can you help me reproduce that crash it so I might find you a workaround or advocate for getting it fixed?

Thanks,
Lanette

Thanks. I do understand there’s no point in replying to every single crash people report. However there was one particular crash report where somebody (you?) actually responded and requested more information, which I sent, that is probably the one you’re referring to.

Allow me to suggest we get back to those crashes once the two more pressing bugs are fixed: exporting documents, and accessing shared layers. Both are simple to reproduce, and it’s probably really simple to determine what causes them, so this is the place where you lobbying might go first. Since the crashes happened when I was tracking down those bugs, they might very well disappear with the bugs. That would save us both some time, right?

I reread your post with all the information 6 times (no exaggeration) and I did not understand that those were the 2 things blocking you. Also, when you say exporting, do you mean you can’t export anything. Can you export “Hello world” from a simple single layer? Or that you can’t export with groups and shared layers and tables and subgraphs? Because I’m not clear on which case is true.

  1. You can get all of the items in a group, but to do so, you need to iterate over each group to get each member from the group rather than the group itself. You will get an array of graphics back from the group. The same is true for subgraphs and tables which are all groups. This process works for me inside OmniJS, but I don’t know how to do that in JXA. You should be able to get an array of graphics returned for each group object. Examples for both Groups and Subgraphs.

  2. I can get your shared layer from OmniJS. I’m not sure why you can’t with JXA? For example, in your text document, I get every item on the console if I unlock your layer on your last canvas and run

     var groups = new Array()
         canvases.forEach(function(cnvs){
         	cnvs.graphics.forEach(function(graphic){
         		if(graphic instanceof Group){groups.push(graphic)}
         		 console.log(graphic + ' isGraphic ' + Graphic.name)
         	})
         })
    

You have a huge challenge with 130 illustrations. The best way to get support with scripting is one small issue at a time. As painful as that is, I don’t see any other way it’s going to be constructive. What you may want to do is use OmniJS inside of Actions for what you need to do in OmniGraffle since the JXA support is more limited, then call the actions from JXA.

Thanks,
Lanette

I’m really sorry for not making myself more clear in my post, I see now that I made this much too complicated by adding all the detail. So it got totally lost when clarified this exact thing in my second reply to you (14 days ago), when I wrote:

But what is blocking my progress is actually bugs in OmniGraffle, which I reported as:

  • OG #2173060 “Problems with JXA accessing items in OmniGraffle documents” (sent about a week ago, nothing but an auto-reply so far)
  • OG #2166190 “Omnigraffle: Export and JXA” (about a week ago) (which is very likely related to a problem I reported as OG #1820090 “OmniGraffle document model” (August 2017)

I had already tried the one thing at a a time approach with the export problem, see this other thread, but nobody replied. That post should contain the info you need to understand that particular problem.

About the shared layers, I’m pretty sure they are broken in JXA, see OG #2173060 for the details about that. From what you explain, things should be different in OmniJS, so I tried again. On my first test I was able to access the item in a shared layers, but the moment I tried accessing the second graphic in that layer (which does not exist), I also could no longer access the first. Then relaunched OmniGraffle and tried again, and now I could trigger this right away:

I’m a bit tired at the moment, so please forgive me if I missed anything obvious, but it appears to me that while I can access both objects in the layer that is not shared, I cannot access the single object in the shared layer. And the error message is the same as the one I described in the post above. I did not run any JXA, or a plugin or similar. The test file is the one I used in the post above.

When I attempt to copy the circle in the shared layer as JavaScript, I get this:

// Floating point values in this script may be rounded, resulting in minor visual differences     from the original
var canvas = document.windows[0].selection.canvas;
var g1 = canvas.newShape();

However, when I copy a circle in the first layer (not shared), I get this:

// Floating point values in this script may be rounded, resulting in minor visual differences from the original
var canvas = document.windows[0].selection.canvas;
var g1 = canvas.newShape();
g1.textAlongPathGlyphAnchor = 0;
g1.text = "";
g1.textVerticalPadding = 5;
g1.fillType = FillType.Solid;
g1.plasticHighlightAngle = null;
g1.cornerRadius = 0;
g1.flippedVertically = false;
g1.name = null;
g1.blendColor = null;
g1.textRotation = 0;
g1.shape = "Circle";
g1.fillColor = Color.RGB(1.0, 1.0, 1.0);
g1.strokeCap = LineCap.Round;
g1.textUnitRect = new Rect(0.10, 0.15, 0.80, 0.70);
g1.tripleBlend = false;
g1.gradientCenter = new Point(0.00, 0.00);
g1.strokePattern = StrokeDash.Solid;
g1.geometry = new Rect(125.00, 82.00, 316.00, 288.00);
g1.textRotationIsRelative = true;
g1.shadowColor = null;
g1.shadowFuzziness = 3;
g1.notes = "";
g1.locked = false;
g1.allowsConnections = true;
g1.strokeJoin = LineJoin.Round;
g1.imageOpacity = 0;
g1.blendFraction = 0;
g1.flippedHorizontally = false;
g1.rotation = 0;
g1.imageScale = new Size(0.00, 0.00);
g1.textColor = Color.black;
g1.automationAction = [];
g1.autosizing = TextAutosizing.Overflow;
g1.magnets = [];
g1.textVerticalPlacement = VerticalTextPlacement.Middle;
g1.strokeType = StrokeType.Single;
g1.actionURL = null;
g1.gradientAngle = 90;
g1.gradientColor = Color.RGB(0.20000000298023224, 0.20000000298023224, 0.20000000298023224);
g1.strokeColor = Color.RGB(0.0, 0.0, 0.0);
g1.image = null;
g1.userData = {};
g1.imagePage = 0;
g1.textSize = 16;
g1.shadowVector = new Point(0.00, 2.00);
g1.textWraps = true;
g1.plasticCurve = null;
g1.strokeThickness = 1;
g1.alignsEdgesToGrid = true;
g1.textHorizontalAlignment = HorizontalTextAlignment.Center;
g1.textHorizontalPadding = 5;
g1.imageSizing = ImageSizing.Manual;
g1.fontName = "HelveticaNeue";
g1.imageOffset = new Point(0.00, 0.00);

I’m curious if you can confirm this on your machine.

Yes, we agree that the things you reported are broken in JXA and it’s an active bug. Yes, I get the same result when I Copy as JavaScript on a shared layer, and that is because this bug goes far beyond with just JXA. Sorry that it took me some time to figure out what was going wrong. I will get these issues reported to the team and make sure I update the scope of the initial bug you reported. You can’t get anything from a shared layer at all, which is the single reason you are blocked. Let me work on this here and see if I can find anything out. I will talk to some people on Monday and see what I can learn.

Here is my revised script for figuring out what is going wrong with getting the text out of OmniGraffle. I run this in the console to see what is returned for the graphics in the frontmost document:

var groups = new Array()
     canvases.forEach(function(cnvs){
     	cnvs.graphics.forEach(function(graphic){
     		if(graphic instanceof Group){groups.push(graphic)}
     		 console.log(graphic + ' is Graphic ' + graphic.name + ' has text ' + graphic.text)
     	})
     })

Next week, by Friday, I am going to send you an email including some ideas on how you might proceed after I talk to some colleagues. Please keep in mind that I am not a support professional, which is why I am working on this on the weekend, as I still have to do my regular work. I use the OmniGraffle JavaScript library and I’d like to see us improve it. Please be patient with our support humans. Fewer of us code in JavaScript here than in other languages, so it does take us longer than a typical question to answer.

Thanks,
Lanette

Lanette,

my sincere thanks for your continued support, for sticking with this even though I probably did not make it easy for you, and for working on this in your free time, even though this is not your job. I appreciate what you do, and at the same time I feel this is not the way it should be, it’s not a sustainable way for an organization, and there’s obviously an underlying structural problem that needs to be addressed. Good luck with that.

I gather you are working as a tester, so may I in return suggest something that would probably make your life on your regular job a bit easier: both JXA and OmniJS lend themselves for automated testing, and basically that’s what I did when identifying those bugs: I created a test document that contained all the features I used in my illustrations (shared layers, formatting etc.), and then I wrote code to see how to access those features. It’s pretty simple to use that idea and create a suite of automated test that runs on your CI system and makes sure all the parts of the OmniJS API are up and running. For OmniJS, the obvious way to trigger tests is Script Links. For JXA, you can run osascript from the command line. For JXA you could even use Espresso through node-jxa.

The next update to OmniGraffle (v7.11) has fixes for accessing graphics on shared layers both from Omni Automation (OmniJS) and from Apple Events (including AppleScript and JXA).

I anticipate that test builds of OmniGraffle 7.11 will be available within the next week or so.

1 Like

Test builds with these fixes are available now!

https://omnistaging.omnigroup.com/omnigraffle/

Please let us know if you encounter scripting bugs in these builds; at this point in the test cycle, we should be able to turn around additional scripting fixes fairly quickly.

1 Like

First test reveals: export is still broken in 7.11 test (v196.1 r330841).

e.g. this

is not the same as this

or this

is not like this:

As the forum does not allow me to upload a zip, I’ll send it over via email so you can test for yourself.

Hi,

I created a small OmniJS API test suite to check for the stuff I was aware of, and it appears that while I can access graphics in shared layers now via iterating through the graphics collection of that layer, canvas.graphicWithName() appears to be broken in shared layers.

You can download the test suite and the document it uses and see for your self. Simply install the plugin, open the document and run the plugin. Look at the console to see test output.

Can you confirm this is broken, or was there something I missed?

I have not tested all this in JXA yet, just in OmniJS, and here’s some other bugs and glitches I found:

  • export via JXA still does not work (see my export script)
  • Omnigraffle Undo does not track changing object names
    • OmniGraffle is quite slow to update a plugin, it frequently happened to me that I triggered the tests after replacing the plugin, and OmniGraffle still ran the old plugin code
  • graphics in subgraphs can be accessed both via graphics and subgraphics collections. I think that is weird, and the array subgraphics is unnecessary. That’s the one place I am aware of where a graphics collection has a different name.

Thanks for reporting the problem! It might be helpful to split these issues into separate threads, so the circumstances are more clear. I agree that this shows that export is broken, but I was left guessing as to how you got that set of results: export seemed to be working fine in my own tests.

Since my tests were working and yours were not, even with the same input document (thank you for providing that in your github repository!), I did some digging to try to track down why my script was working when yours was not.

It looks like these results came from your JXA export script, and the rest of this post is based on that assumption. If I’m wrong about that, please let me know!

The difference between your script and my quick test was that your script was immediately closing the exported document. This is totally a reasonable thing to do! I’m not super familiar with this portion of our code base, but it looks to me like our export operation is not fully synchronous—and closing the document before the operation completes leads to an incomplete rendering of the content in the export. (My first clue that this was happening was that when I created a test document with two identical canvases, and the first exported fine while the second did not.)

When I modify your JXA script’s exportDocument() function to comment out the doc.close() instruction, I seem to be getting much better results. Can you confirm whether this helps your exports get better results?

This is obviously a bug, and I consider it a high priority issue since it means your script cannot reliably work with the data from the export operation—and it has no easy way of knowing when that operation is actually complete.

Returning to the original problem statement for JXA scripting:

I believe this is due to the bug I just identified where closing a document can interrupt an export that hasn’t yet completed (and there’s no way to know when it’s completed). For now, the workaround on this is to leave those documents open.

This problem is solved in the current v7.11 test builds.

The issue you’re running into here is that you’re passing OmniGraffle a string rather than a Path object. You need to use a Path so that the sandboxing system knows that your script has granted OmniGraffle access to the file in question. (Pre-opening the file is a workaround because opening the file also lets the sandboxing system know that it should grant OmniGraffle access to that file.)

If you use OmniGraffle.open(Path(file)) (much like you’re already doing in the to parameter to your export command), you’ll find it solves this issue.