The Ruby API to Google SketchUp has a function, open_file, but I can't find a close_file function. Since I have to batch process many files, I want to close each file before moving on to the next, otherwise the program will crash from memory exhaustion.
What is the best way to close SketchUp files programmatically?
I am using Mac OS X and am willing to use AppleScript functions to signal the window to close.
EDIT
I am considering a few approaches that have proven fruitless so far.
Using the appscript Ruby gem, as described in this question. The problem here is that I cannot get SketchUp to recognize my installed gems.
In a similar vein, I am trying to use osascript (a bash program that executes AppleScripts from the shell) to close the window. That is, I call out to the shell from SketchUp's Ruby console window using one of the following:
%x[osascript -e 'tell application "SketchUp" to close window 1']
%x[osascript -e 'tell application "SketchUp" to close window 1' &]
%x[osascript -e 'tell application "SketchUp" to close every window']
%x[osascript -e 'tell application "SketchUp" to close every window' &]
Whenever I try this second approach, SketchUp just freezes. However, when I execute any of these commands from an IRB or directly from the Bash prompt outside of SketchUp, I get the desired behavior: the model window closes (incidentally, the Ruby console window remains open, which is fine).
Have a master script that launches a slave script to process each model. The slave will run within the Google SketchUp program while the master waits. When the slave is finished, it signals the master, and the master closes the SketchUp file. To do this interprocess communication, I tried using drb. However, when I try to require drb within SketchUp, I get the following message:
Error: LoadError: (eval):5:in 'require': no such file to load -- drb
EDIT 2
Having a separate process continuously running that closes Google Sketchup windows using AppleScript when signaled is clumsy for a number of reasons. First, it's ugly to have to have a separate process devoted to closing Sketchup windows. Second, the only effective way of communicating with the external script is through the creation of files, which is wasteful and the disk access may be slowing things down.
However, the most severe issue is that Sketchup is slow at responding to AppleScript commands. I have a pretty computation intensive script running in Sketchup, and it seems to starve the AppleScript response, which means that the osascript times out before the windows close. Sketchup only gets around to responding to AppleScript when there is a dialogue box prompt in Sketchup that pauses the execution of my computationally intensive script.
EDIT 3
I have modified my close_file function to pause execution of the script by displaying a dialog box. This essentially yields the current thread and allows the thread that responds to AppleScript commands to execute:
def close_file()
f = '/temp/mutex.txt' # for finer control, use different mutex for each window you want closed
File.new(f, 'w').close
result = UI.messagebox "Click OK when window has closed."
end
Then the separate ruby script that closes windows via AppleScript will additionally have to click "OK" in the dialog box. AppleScript to do that is:
tell application "System Events"
tell process "SketchUp"
set frontmost to true
keystroke return
end tell
end tell
This modification is an improvement. It corrects the "most severe issue" mentioned in EDIT 2, but the other issues remain.
There is an important limitation with using an external AppleScript to force SketchUp to do something. SketchUp will not accept any user input while a menu script is running.
I found this out trying to setup an automated rendering solution. I added a menu item which opens a WebDialog to get a list of models to download from a server. It then steps through the list and uses cURL to download each model. Once downloaded it loads the model, renders it out, uploads the images using cURL again, and goes to the next model.
What I wanted was to activate the AppleScript after each model is rendered (using the "mutex" file solution shown above) and have it close the model window. Unfortunately, since the menu script is still running, SketchUp will not respond to anything the AppleScript tells it to do. Only once all of the models have been processed and the menu script exits, will the AppleScript finally run.
This would not be so bad if all of the calls to the AppleScript were queued up, but they aren't. It seems that only the last two get honored (that number may be just an accident dependent on the timing of when my calls to the AppleScript were made relative to when the menu script finished).
Because of this limitation, I was not able to use the mutex wait() function in my menu Ruby code. Since the AppleScript was not able to execute, it never created its "I'm done" mutex file, and therefore the wait() function in Ruby never got the signal that it could continue. The result was a deadlock between AppleScript and SketchUp, such that SketchUp just freezes up (well, actually it is just stuck in a really tight loop in the wait() function).
So, when approaching this problem, think of AppleScript as a cleanup tool you can call when all of your processing is done.
One solution is to have a separate process continuously running that will close Google Sketchup windows using AppleScript when signaled. The approach described here uses files to do the interprocess communication (drb did not seem to work).
First, you have to make sure Sketchup supports AppleScripting. To do this, execute the following at the command prompt (with appropriate substitutions for different Sketchup versions, etc.):
$ defaults write /Applications/Google\ SketchUp\ 8/SketchUp.app/Contents/Info NSAppleScriptEnabled -bool YES
Now, create a separate file that will close Sketchup windows. Here is my closer.rb file:
#!/usr/bin/ruby
## closer.rb ##
def wait(f)
while !File::exists?(f)
end
File.delete(f)
end
def signal(f)
File.new(f, 'w').close
end
while true
wait('~/temp/mutex.txt')
msg = %x[osascript -e 'tell application "SketchUp" to close every window']
signal('~/temp/conf.txt')
end
Run this script from the shell. It waits until the file ~/temp/mutex.txt is created. Once the file is created, it runs osascript (essentially AppleScript) to close all the windows in Sketchup, and then signals that the windows were closed by creating a ~/temp/conf.txt file.
Here is the client code (which can be placed in your Sketchup plugins) that signals the closer script:
def wait(f)
while !File::exists?(f)
end
File.delete(f)
end
def signal(f)
File.new(f, 'w').close
end
def close_file
signal('~/temp/mutex.txt')
wait('~/temp/conf.txt')
end
The close_file signals the closer script and then waits for a confirmation that the file was closed before returning. Now you can close files in Sketchup.
You can do a lot with SketchUp using applescript and/or Automator.
tell application "SketchUp"
activate
tell application "System Events"
delay 1.0 --close window, adjust delay to suit
--key code 13 using command down -- Press ⌘W or you can use
keystroke "w" using command down
end tell
end tell
I have a number of status bar items for open new, paste to console, closing, killing SU etc.
They're tiny files that trigger the SU shortcut keys, and can be used externally, from 'Ruby Console' or as part of a plugin.
I'm not sure what kind of 'batch convert' your trying to achieve, but
I used Automator to convert most of my old v5, V6, v7 files to v8 today...
100's of them,
and your probably aware, on opening you get 'alert' warning that saving will convert the file to the latest version (which is what I wanted).
It's tricky to click this "OK" programatically.
Automator on it's own is quite restricted, but you can add an applescript to the workflow and I've found that if you use it's 'record function' to demonstrate what you need to do.
You can then copy/paste that code into 'Script Editor', get rid of the 'dowithTimeout' bits, copy it back into the workflow with delays either side and let it run.
I had to play around with delays, to accommodate some larger files, but achieved >95% success. [Success being able to select all, right click, Create Icon and Preview.]
Unfortunately I then binned the workflow, but could recreate it, if you want to have a look.
success, in Ruby Console
system("osascript -e 'tell application \"suoff\"' -e 'activate' -e 'end tell'")
and this script inside an Automator.app in applications folder....
on run
-- Make sure SU is foremost, don't click on anything else either
tell application "System Events"
tell process "SketchUp"
set frontmost to true
-- gives a message if you try to select when SU's not running
delay 0.2
tell application "SketchUp" to quit saving no
-- no dialog box etc...
delay 0.1
end tell
end tell
end run
the problem is SU has an anti self destruct ruby, that mines any linked files and aborts all efforts to shut it down... from inside itself.
so you need to bury it...
this combination works on 10.5.8 with SU8.1.
if you have a look at SketchUcation [Developer] Forum, I left a call for mac/applescript testers... I've rewritten the app since then but if you PM me I'll send you an open copy of all the bits I've got working..
john
I had to convert hundreds of .skp files with a batch processing script. I faced similar issue, since every time I opened a new file it remained opened. For me it was enough to close the active_model and the file goes with it.
This was true for SketchUp 2019 Pro Mac OS X version.
model = Sketchup.active_model
model.save(filename, Sketchup::Model::VERSION_2015)
model.close
Hope it will help
Related
Thanks for reading my question. I have an issue I was hoping you could help me with. the following apple script will run but ends up timing out because the application is not selected; the icon just bounces on the mac server dock (OSX 10.85). What am I doing wrong?
tell application "(Application Name)"
activate
getURL "(Server URL)"
delay 30
tell database "(database name)"
do script "(Script Name)"
delay 60
close
end tell
end tell
do shell script "(Shell script path)"
Also, I'd like to tell the application to quit prior to running the shell script.
Any and all advice would be appreciated.
THANK YOU!
To quit an app, all you do is put a “quit” command inside its tell block, typically as the last command in the tell block:
tell application "Safari"
quit
end tell
Verify that “getURL” is the right command for the app you are targeting. The modern version is “open location.”
When you run your script from AppleScript Editor, you can tap on “Event Log” at the bottom of the window and see a log of events that occurred during your script’s execution. You can often see what went wrong there.
If your AppleScript is what is timing out, you can use with timeout:
with timeout of 3600 seconds
-- do something within an hour
end timeout
As jweaks said, you need to provide more information to get real help. Every application extends AppleScript in its own way. Your script may be perfect or it may not. There is no way for someone to know without knowing what application your script runs on. It’s like asking for help with Photoshop work but not telling anyone it is Photoshop you are working in.
Say I have two applications running; App A and App B. What would be the easiest way (or indeed is there anyway) to get App B to close automatically when App A is closed? Note that neither of the apps in question have been developed by me and so I have no control over their internal behaviour.
I am open to any suggestions including those that entail the use of Applescript, Automator, Terminal commands and BASH scripting. I would even consider developing a lightweight Mac OS X application to achieve this.
If you don't need B to exit immediately - if it's OK to wait a few seconds - then you could schedule a periodic background task (using cron or even just iCal) that does something like this:
if not exists (processes where name is A)
tell application B to quit
end if
Another option, if you want an immediate response, would be to wrap App A in a script that launches it, waits for it to terminate, and then terminates B (osascript -e "tell application B to quit"). Then you could just always use that script to launch A.
You could even insert the script into the application bundle so that double-clicking runs your script. You would do this by doing "show package contents" on the application, replacing the <CFBundleExecutable> in <app>\Contents\info.plist with your script name, and dropping that script into <app>\Contents\MacOS. Then have the script just run the executable that is already there.
Fantastic question. I spent about 10 minutes looking for an old project where I had registered for notifications for when applications quit but couldn't easily find my code. But I did find a potential alternative for you.
If you download Apple's AppList sample code project, you'll see that it is observing the list of NSRunningApplications and when an app quits, it removes that app from the list of running apps in the window. You can take the technique they're using there and when you detect your "application A" quits, you can send a "quit" Apple Event to "application B".
Since you are running Lion, you can use a Cocoa-AppleScript to access Cocoa methods to add your application as an observer, getting notifications when applications quit.
For example, create a new application from the AppleScript Editor > File > New from Template > Cocoa-AppleScript applet. In the run handler, add the application as an observer to get notifications when an application quits:
set theNotice to current application's NSWorkspaceDidTerminateApplicationNotification
tell current application's NSWorkspace's sharedWorkspace's notificationCenter
addObserver_selector_name_object_(me, "appQuitNotification:", theNotice, missing value)
end tell
Next, add an appQuitNotification handler (this is the handler selector specified in addObserver_selector_name_object_ above), something like:
on appQuitNotification_(aNotification) -- an application quit
-- aNotification's userInfo record contains an NSRunningApplication instance that we can get properties from
set theApplication to (aNotification's userInfo's NSWorkspaceApplicationKey's localizedName()) as text
say "application " & theApplication & " quit" -- for testing
if theApplication is "this" then tell application "that" to quit -- or whatever
end appQuitNotification_
...and you are done. As long as your application is running, it will get notifications from the system when an application quits. Note that these Cocoa-AppleScript applications can't be run directly from the script editor, so they can be a bit of a pain to debug since there is no event log to look at - you will need to add your own dialogs or whatever.
I have a semi-long AppleScript that I run each morning to launch all of my apps etc. One of the things that it does is launch a few apps and then immediately minimize them. When I paste the .applescript source into Script Editor and run it, everything works fine:
-- snip:
tell application "Mail"
launch
minimize(window 1) of me
check for new mail
end tell
-- 'minimize' defined as:
on minimize(w)
set the miniaturized of w to true
end minimize
But when I compile the AppleScript source as follows:
osacompile -o ~/Library/Scripts/myscript.scpt myscript.applescript
... the compiler munges minimize to be:
on minimize(w)
set |miniaturized| of w to true
end minimize
And I get this error:
error "Mail got an error: Can’t make |miniaturized| of window id 30936 into type reference." number -1700 from |miniaturized| of window id 30936 to reference
Anyone have any clue what I'm doing wrong here? For purposes of version control, I need to run the scripts through osacompile.
UPDATE: To clarify, what seems to be happening is that Script Editor is compiling the method differently than osacompile on the command line. Is it known whether they compile different (e.g., using scope inferences or some such thing)?
There is nothing wrong with your code - I suspect this is a bug in osacompile and I suggest you file a bug report with Apple - as I've done.
You can verify that your code works correctly by using AppleScript Editor to save it as a *.scpt file directly and then running it with osascript.
[Updated] By contrast, passing the *.applescript source-code file directly to osascript does exhibit the problem.
There is no good reason I can think of for AppleScript Editor-based compilation to work differently from osacompile (and on-demand compilation in osascript), and the former's behavior is the expected and desired one in this case.
There are 2 workarounds:
Enclose the reference to miniaturized in a using terms from application "System Events" block:
This is a generic workaround that should work with windows from any AppleScriptable application.
on minimize(w)
using terms from application "System Events" # WORKAROUND
set miniaturized of w to true
end using terms from
end minimize
Inline the miniaturization command instead of calling a subroutine:
set miniaturized of window 1 to true
on minimize(w)
set the miniaturized of w to true
end minimize
This is not correct. You have "miniaturized" outside any application tell block of code, which means that it's an applescript command. It isn't an applescript command though. It's a command of Mail and other applications. It's amazing that it works properly in AppleScript Editor. It really shouldn't. When you have a command that doesn't make sense applescript is good enough to try to make sense of it and sometimes it can overcome your coding mistake. Obviously osacompile can't overcome your coding mistake.
So the way to fix the osacompile issue is to eliminate the coding mistake. You need to have the miniaturized command inside of an application tell block of code.
To see what I mean open the "standard additions" applescript dictionary and try to find the miniaturized command. you won't find it. Now open Mail's applescript dictionary and you will find it. Thus the command belongs to Mail, not applescript.
I'm using a Mac, OSX 10.6 and I have a function in a desktop application that I want to automate. Manually I press Command+R wait for the application to read some data form a physical device for 1 minute, then press command+R again to take another reading (at this point it asks me if I want to save the data, so I press tab, tab and then space bar to select to save the data. I do this 3 times in total, so I want to automate the 3 times, so I can walk away from the computer and it will read 3 times automatically.
Is automator the best way to do this?
I've tried to do this already in automator by using the 'watch me do' function. This starts with the 'bring window Untitled to the front', and then the second command is press command+R. I then found a little piece of apple script to wait 1 minute and I plug the first action into that for the wait function.
However, when I click run or step, instead of going and opening up the correct window ("Untitled"), the cursor moves to the Media button in automator, and clicks that instead! But the application is definitely listed as the correct one.
Any help appreciated, but maybe automator is the wrong way to go?
Apple Script is the best way to go for things that don't require any "special processing" that would need to be done by a chain of different applications.
1) uging the AppleScript Utility
make sure that you have GUI scripting enabled in the "AppleScript Utility"
2) using the Script Editor choose File>Open Library and see if your application has any scriptable functions ... these may be a better way to go.
3) Create a new script and put in something like this ...
tell application "Firefox"
activate
delay 1 -- give it time to react
repeat 3 times
-- this gives us the keyboard
tell application "System Events"
keystroke "r" using {command down}
end tell
delay 6
end repeat
end tell
I used Firefox to test it .... should work for you ....
Once you've got the script you can use the save as to make it into an app or save it as a script in your ~/Library/Scripts folder or paste it into an automator workflow and schedule it with iCal.
I don't think automator is the way to go. You could use applescript, but you should take a look at sikuli. You will need to write the Sikuli script yourself, but what you describe shouldn't be difficult
Specifically,
In OSX 10.6 from a system call, I want to open a file for editing with VIM in a preexisting terminal (i.e. Terminal.app) by opening a new tab.
Of course I can open a new instance of terminal
/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal -e vim MyFile
And, of course I can figure out the PID of the running instance of Terminal but I don't know how to pass a command into that running program. Or, if Terminal supports receiving commands and if it will open a new tab.
If someone knows how to do this with a similar system (e.g. linux and xterm) it could help me figure it out with OSX and Terminal - or, is there some other technique to prevent opening so many terminals instances?
EDIT: CHEAP SOLUTION
I created an AppleAcript script
on run app_arg
tell application "System Events"
tell application process "Terminal"
key code {55, 36}
set frontmost to true
key code {55, 17}
keystroke item 1 of app_arg
keystroke return
end tell
end tell
end run
and run it via the system call like so
/usr/bin/osascript NEWSCRIPT.scpt "args"
It's dirty but it gets the job done - thanks!
The way to accomplish the is with applescript. You can send applescript to things in OS X with the osascript command. I wasn't able to find anything quickly that directly shows how to open a new tab with a command running in it, but I was able to find a couple of references to automating Terminal.app in various other ways with applescript, and I think they may point you in the right direction.
Various random Terminal.app applescript hacks mostly centered around changing colors.
An applescript hack that opens a new terminal window without creating a new Terminal process.
A nice StackOverflow question about how to query an application to discover what applescript it supports (stolen from a comment on your question by #dmckee)
And this nice StackOverflow question concerning Terminal.app's applescript specifically (again stolen from a comment by #dmckee)
An even better exploration of the Leopard Terminal.app's applescript from Ruby no less
And from that last link, it looks like the only way to do it is to use applescript to send the Command-T keystroke to the terminal. That's ugly, but it'll work. And then you can send the command you want to execute. :-)
There are three ways to do this:
Use popen
Use system
Use exec family