Equivalent of robofab.RFont.update()
-
Matthew:
My guess as to why it's working the same way (Frederik can confirm) is that RoboFont is smarter about updating the UI than FL is. You'd want to use
.update()
in the case you want to be absolutely sure the UI updates, and/or your script needs to run in both RoboFont and FontLab, and need it in there to force the FontLab UI to update.Best,
Ben
-
thanks for proving it yourself! ;)
as stated in the documentation
.update()
only tells the UI that your object has changed.see http://doc.robofont.com/api/robofab-extras/
off topic:
I've just edited your post and added<pre>bla bla bla</pre>
for none-editors and none-admins
-
GAAAH
(How do I include a pre tag in a comment? Apparently it’s something different than typing greater-than + pre + less-than)
-
(How do I include a tag in a comment? Apparently it's something different than typing )
-
Fair enough. At the moment, I don't have another script in my back pocket that illustrates the need for incremental UI updates. If I come across one, I will post it. Like I say, mostly I have used the feature for status reporting and debugging.
As for
update()
, my only remaining question is when it actually makes a difference in a Robofont script. For instance, this code snippet (adapted from the EVB script) works the same way with or without the calls toupdate()
.from robofab.world import CurrentFont f = CurrentFont() for k in ["A", "B", "C", "D"]: f[k].mark = 100 f.update()
from robofab.world import CurrentFont f = CurrentFont() for k in ["A", "B", "C", "D"]: f[k].mark = 100
-
Matthew:
With all apologies, I don't think that this is a policy question.
I think that we can agree that
.update()
in both FontLab and RoboFont and Glyphs (for all I know) updates the UI. Code with.update()
works in both FontLab and RoboFont, with the result that at the end of a script, the UI is updated for the font or glyph that it is called on.In FontLab, it happens that if you call
f.update()
, FontLab will refresh the UI for each incremental change if it is called in a loop. This, from a speed of the application viewpoint, is a really dumb thing to do, as it slows everything down to molasses speed (see Frederik's timing and Tal's comment about when RoboFab was too liberal with.update()
— and, if you've used UFO export in FontLab through RoboFab before the.update()
s were taken out, you know that doing so could mean a nice coffee break). The only use of this 'feature' of FontLab, which you mention, that I can see would be for testing one's script. After the testing stage, one would just want their script to run, and run as quickly as possible. Frederik has demonstrated code that one could insert into a script for testing, much like how one inserts several print statements when testing a script, then takes them out when things are working.You pointed to Erik van Blokland's build accent script, which calls
.update()
so that FontLab will refresh the UI and show all the built accents. This script will work the same way in RoboFont, as.update()
will also update the UI. Things work well in both applications. I believe, that what Tal, and for that matter, I, am curious about, is a example where a script depends on showing the UI update for each point in a loop, not just needing to update when all transformations are finished.I think that we can agree that FontLab isn't always the most consistent of applications, and that a view of consistency of RoboFab meaning that it needs to behave, bugs and all, 100% exactly like RoboFab in FontLab, even if the end result is the same, is both impractical and akin to asking every single web browser to behave just like IE6.
The end result of a
.update()
is the same, which is all I think one can expect and ask for. If.update()
's end result was different in different applications, this would be a problem for RoboFab, but this is not the case here.
-
My understanding of why
RFont.update()
exists & how to use it was gleaned entirely from the Robofab docs & examples. Some Robofabbers may use it more, some less. But it is part of the standard Robofab-in-Fontlab scripting idiom. If you followed the Robofab docs, your scripts rely on it.(Tal, since you ask, one example of a script that "needs to update the UI immediately" is EVB's accent builder example on the Robofab site. (http://robofab.com/howto/buildingaccents.html) And I try to learn from the master.)
As a matter of policy, having
update()
do nothing in NoneLab makes logical sense because there is no UI to update.Likewise, my comment is also one of policy: now that Robofab is in another UI environment, how should
update()
behave? Does it matter that it behaves differently than it did in the previous UI environment?Ben says no, because
update()
was designed to accommodate FL and I can test for FL in the script. A fair point, but there's another dimension to the problem. Theupdate()
command gives Robofab-in-Fontlab a nice modular division between the Robofab layer and the Fontlab UI layer. Everything gets encapsulated into theupdate()
command. I don't have to worry about the specifics. Maybe this was an unintentional result, but to the extent that one of Robofab's goals is to insulate me from the FL object model, I always thought it was deliberate.Above, Frederik showed me how to work with view objects and
view.display()
to do my updates. That's fine as far as it goes, but it lacks modularity. I've got a script that would be happy if it could just be in the business of manipulating Robofab objects. Now it has to get into the business of manipulating Robofont UI objects too.As a scripter, it's not that I mind manipulating UI objects. It's just that ideally, I'd like to avoid mixing UI stuff with data stuff, because it won't scale. For instance, what happens when folks start releasing scripts with new UI widgets? Now my data-manipulation script has to get updated to work with those widgets too. (Tal, perhaps your proposed approach is forward-compatible in that way.)
Nor am I saying that Robofont or defcon does it "wrong".
Again, it's a policy question of how to expose the Robofont UI to scripts, whether to be consistent with FL, or whether to invent a 3rd idiom (i.e., require Robofab-in-Robofont scripters to take more of an interest in Robofont UI objects).
-
works!
but it will never make it into RF, sorry :)import time def update(font): document = font.document() for windowController in document.windowControllers(): window = windowController.window() contentView = window.contentView() contentView.displayIfNeeded() f = CurrentFont() g = f["A"] s = time.time() for angle in range(0, 360, 15): g.rotate(angle) update(f) print time.time() - s s = time.time() for angle in range(0, 360, 15): g.rotate(angle) f.update() print time.time() - s
(with a font window open, glyph window open and Space Center open)
For such a simple task it takes one 1 sec more to update.
>>> 1.04617500305 >>> 0.0473890304565
So this is not a good plan unless you realllllyy like coffee or having days with 25 hours or when the sun is turning half speed....
if this is the case you can install a script during startup that add an observer to a
fontChanged
notification and updates all the possible windows and views.
-
I thought of a way that this could be done in the font and glyph objects. Here is the font object version:
def update(self): document = self.document for windowController in document.windowControllers(): window = windowController.window() contentView = window.contentView() contentView.displayIfNeeded()
(Not tested, but it should work. I think. Maybe.)
Is this a good idea? I don't know. It could slow down lots of scripts for the sake of one script.
-
Because
.update()
is a method that was added to RoboFab to work around FontLab UI bugs, I don't think that it's fair to assume that it should work the same way in an environment where those UI bugs don't exist.If you want to match the behavior of FontLab's
.update()
in RoboFont, you can put a test in your script for the enviroment the script is running in (robofab.world.inFontLab
orrobofab.world.inRoboFont
) and useview.display()
if the script is running in RoboFont.
-
I can't answer for RoboFont, but I can explain what is happening at the lower levels in defcon and defconAppKit. I can also give a little background on the history of the update method.
First, the background: the update method in FontLab is purely a UI refresher. When you make a change to the data, the data is changed but the UI is not. For many scripters, this creates a pretty big problem because if update isn't called, the UI is out of sync with the data. That's bad. If you look at the RoboFab docs and examples, you'll see lots of things like
# don't forget to call update!
Those are there because we had lots of complaints. The update methods weren't part of RoboFog's scripting API and that's what we were trying to mimic. Almost all of the scripters were familiar with that API (and things happening there automatically) and that made the update calls quite foreign and a source of many complaints. We spent a lot of time trying to make the updates happen automatically, but we never got any responses to our requests to FontLab for information about the optimal times to refresh. For a very short period, we had update happening automatically whenever any data was changed. This was known as the OMG THIS IS SO SLOW version of RoboFab.To make the RoboFab API consistent, we implemented the update method in the base objects as:
def update(self): pass
It has been that way in NoneLab for a long time.
When I wrote defcon, I wanted to do things in a modern way that allowed for code to be much more flexible and much faster. defcon uses the observer pattern (concept: http://en.wikipedia.org/wiki/Observer_pattern implementation: http://code.typesupply.com/browser/packages/defcon/trunk/Lib/defcon/tools/notifications.py) for keeping data and representations in sync in an abstract way. When you make a change to a defcon object, the object posts notifications through a font level notification dispatcher. Other objects that have subscribed to
(object, notification)
pairs will receive the notifications. It makes everything very lightweight and very fast. The various defconAppKit (an extension of vanilla) views use the notification system to be alerted of font object changes. When particular notifications are posted, defconAppKit notices and schedules things to be redrawn the next time Cocoa gives the views permission to redraw. Cocoa does this following its own rules. There are ways to hack into Cocoa to force it to redraw at a precise time, but those are frowned upon. I think this has more to do with the general drawing system that OS X uses than it is that Apple thinks that their code is much faster than everyone else.It's interesting that you say that this need to update the UI immediately is so critical. In all my years of working on and with RoboFab in FontLab I've never seen a script that needed to update the way that you describe. Do you have a lot of these kinds of scripts?
-
I see what you mean Frederik, but I still think this is a flaw, for two reasons.
- The discrepancy between the behavior of
update()
in Fontlab vs.update()
in Robofont reduces the portability of Robofab scripts and makes it harder to create modules that can be shared between environments. Robofab becomes incompatible with itself.
If Robofab-in-Robofont has some extras compared to Robofab-in-Fontlab, that's OK, because can ignore those if I need to. But I wouldn't expect major subtractions either.
RFont.update()
is critical and frequently used in Robofab-in-Fontlab. Once I add extra code to makeupdate()
work in Robofont the way it did in Fontlab, then I can't use that script in Fontlab anymore.- More broadly, an environment like Robofont that's built with a focus on scripting should always defer to the scripter. For instance, if I'm making valid
update()
requests in the script, why should Cocoa "ignore" them? OK, Cocoa thinks it will be slow. But why does Cocoa get to have an opinion? If it runs slow, that's my problem. Maybe I have my reasons for wanting it that way. It should be WYSIWYG — What You Script Is What You Get. There should never be situations where lines of script are getting silently ignored.
(BTW, the animation is very exciting of course, but
update()
has a real practical function, which is to give visual feedback during multi-step operations. It is an excellent debugging and status-reporting tool.)
- The discrepancy between the behavior of
-
mmm, when there are several requests to update a view close after each other, cocoa will ignore previous request and refresh / update the view only once.
but you can force the view to update / refresh:
import robofab.world f = robofab.world.CurrentFont() g = f["A"] collectionView = f.document().getGlyphCollection() view = collectionView.getGlyphCellView() # get the glyph cell view for n in range(0,54): g.rotate(10) g.update() view.display() # force the view to redisplay
hope you like the animation :)
-
OK, then here's an example illustrating my confusion:
import robofab.world f = robofab.world.CurrentFont() g = f["A"] for n in range(0,54): g.rotate(10) f.update()
I expect this code to rotate the A glyph 540 degrees in 10-degree increments, and that each step would be visible in the UI (because I'm calling
update()
after eachrotate()
.)Instead I only see the final state (a net rotation of 180 degrees). In other words, the code behaves as if
f.update()
doesn't exist, and then the UI updates itself after the script exits.So is there a flaw in
f.update()
, or a flaw in my understanding of what it does?
-
there is a
myFont.update()
:)that is posting a font is changed notification to all the subscribed objects
see http://doc.robofont.com/api/robofab-extras/
(just seeing that the
glyph.update()
is not listed in the docs, will add this as well)