Out of focus dialog when calling non-Ruby script on Windows

I have a UI::Command that when invoked displays a UI::HtmlDialog with various HTML elements like sliders and buttons. The sliders are used to control the geometry of the model in “real time”, with the catch that a non-Ruby script is invoked every time a slider is updated. However, the extension doesn’t work as smoothly on Windows as it does on the Mac.

The first problem is that on Windows the console is flashing every time the non-Ruby code is called (which I know was documented before, but sadly @eneroth3’s solution did not work for me). The more significant issue, however, is that the UI::HtmlDialog goes out of focus when any of the sliders are updated via the left/right arrow keys, thus making it impossible to update the model upon subsequent presses of the said keys.

Unfortunately, calling UI::HtmlDialog::bring_to_front after the non-Ruby script finished did not help at all (and I tested this on both SU2017 and the latest SU2023 on Windows). There is no need to call this on macOS as the arrow keys work as expected and the extension is really fun to interact with.

Is there anything else I should try or does this sound like a Windows-specific bug? Or maybe the console flashing is to blame for this out-of-focus behavior?

Yes it is the console window that is the culprit. This has been a long-standing issue with Ruby on Windows platform. Once the focus shifts to another application, Windows requires that the user manually give focus back to SketchUp.

Ie, even calling Sketchup::focus only flashes the SU taskbar icon.

What is the script(s) doing that cannot be done in Ruby ?
And do you need return values from the external scripts ?

1 Like

The non-Ruby script is calling a fairly complex Python library. Porting 2K lines to Ruby is doable, but definitely time-consuming and error-prone.

The external script is writing 3D coordinates to a file which are subsequently read by the Ruby extension to update the SketchUp model.

Okay, doing a quick test from SketchUp’s Ruby console …

I created a folder "C:/test"

In this folder I created a batch script, named "test.cmd" viz:

chdir "C:/test"
dir "C:/Windows" > "test.txt"
chdir "%USERPROFILE%/Documents"

Then loaded the following into SketchUp’s Ruby process by pasting into the console:

require 'win32ole' if !defined?(WIN32OLE)

def go
  WIN32OLE.new('WScript.Shell').Run("C:/test/test.cmd", 0)
end

So, then I type go() in SketchUp’s Ruby console to fire the method …

Result: I saw no shell window pop up as I used the intWindowStyle 0 code for no window.
I also saw no change in the focus. The SketchUp Console child window kept the focus.

The "test.txt" file was successfully written with the directory listing of the Windows folder.

REF:


In the above test, the go() method represents a dialog callback proc that would be attached to a dialog instance.

I quickly added a “go” callback (with the shell Run call) to another extension I am working on and the result was the same.

The Html dialog kept the focus and the file was successfully written to the “test” subdirectory.


ADD: In your implementation, you can likely keep a persistent instance of the shell object for reuse by the dialog’s callbacks within your local class or module thus …

@shell = WIN32OLE.new('WScript.Shell')

… and then just call .Run on that as needed.

3 Likes

Thanks a lot, Dan! I’ve tested on both SU2017 and SU2023 on Windows and the console flashing is indeed gone.

However, my program was unable to read the output file generated by the external script, so I had to make the call synchronous by setting the bWaitOnReturn flag to true like this:

WIN32OLE.new('WScript.Shell').Run("C:/test/test.cmd", 0, true)

There are 2 more issues now: first, when adjusting a slider via the arrow keys, there is some sort of lag until the whole scene is redrawn (it’s not perfect on macOS either, but on Windows it does look jarring). Secondly, if I grab the mouse pointer and move any slider, the program takes a few seconds until it generates the appropriate geometry. It is as if the script is getting invoked with many intermediate inputs corresponding to the locations of the slider thumb during the dragging event.

All that being said, none of these issues manifest on macOS using the plain %x[...] syntax to invoke the script. I have a hunch that the WIN32OLE.new('WScript.Shell').Run method is fairly slow, but I haven’t yet performed any profiling to confirm that this is indeed the case. Note that I am keeping a persistent instance of the shell object like you suggested to avoid additional overhead.

Whatever works for the situation. If the script and file write is fast there should be no problem.

One alternative is a timer that keeps checking if the file is accessible or it’s modification time has changed since the last check.

(1) You can try calling view.invalidate to tell SketchUp that the view needs to be redrawn.

(2) Also if the Outliner inspector panel is open whilst creating geometry and the creation of geometry is not wrapped up in an undo operation with the GUI update switched off, then the population of the Outliner tree with new entities can slow down the view.

Yep. MacOS is more UNIX-like as it came from BSD. Ruby was developed on Unix and/or Linux systems. Always has seemed like the Windows port was an afterthought.

But we also need to recognize that some Ruby workflows do not work well when it is run as a subprocess within an application.

1 Like

Sounds like you are listening for the input event on the slider <input>.

If you instead listen for the change event, you’ll get the value when the mouse is released at the end of the drag.

1 Like

Revisiting …

I occurred to me that you can also use a Ruby refinement within your extension submodule to “fix” the backtick method (mixed in from Kernel) which is used by the %x strings …


require 'win32ole' if !defined?(::WIN32OLE)

module Costica # ... or whatever the top level namespace is

  module RefinedWinBacktick

    refine ::Object do

      define_method("`") do |cmd|
        @shell ||= ::WIN32OLE.new('WScript.Shell')
        @shell.Run(cmd,0,true)
      end

    end # class refinement

  end # refinement module

  module SomeExtension

    using RefinedWinBacktick
    # The refinement(s) to ::Object are available within this module
    # because class ::Module is the direct subclass of class ::Object.

    def self.test
      `C:/test/test.cmd`
    end

    def self.test2
      %x[C:/test/test.cmd]
    end

  end

end # top level namespace
1 Like

I tried the first suggestion and it made no difference. I also don’t keep the Outliner open and the way I handle the redraw involves a simple Sketchup.undo operation followed by the function that adds the entities to the scene (which in most cases adds about 10 faces and maybe 20-30 new edges, so nothing very complex).

I would like to keep listening on input instead as I have a textfield right next to the slider and want that to be updated as I move the slider. I will attach two screen recordings to better show the setup I currently have.

I’ve also included some quick and dirty “profiling” to get an idea of how long the external script takes on each OS. It appears that on Windows the non-Ruby script takes 2x longer compared to macOS, so that might explain the contrast depicted in the attached screen recordings.

Adding the screen recording from macOS (as the forum didn’t let me upload 2 videos in one post).

Note that in both recordings I’m interacting with the sliders via the arrow keys when the mouse pointer doesn’t move.

Re-read this bit of mine and realized that this happens on macOS as well. It’s just that on the Mac the lag doesn’t quite exist and moving the slider gives the impression of a close to real-time animation.

For the text field, updating it in the input event handler is fine.

However, nothing prevents you from also attaching a change event handler to fire off the geometry creation (or external script.)

That didn’t seem to help at all (in fact, I think it might add even more overhead).

Yesterday I ran another experiment to see how slow my script actually is. I simply called print(2 + 2) in my Python script and there was hardly any difference in terms of runtime… I was thinking that maybe the import statements or the actual code were slowing down the execution, but it looks like the Run method on the WIN32OLE.new('WScript.Shell') object is painfully slow on Windows.

That being said, would spinning up a local server and sending a request to it on every slider change help at all? The core would still be implemented in Python, so maybe I won’t see any improvements with this approach either.

In the meantime I will accept Post#4 as the answer to my original question (with the added mention that one should not call the external script often or else the UX won’t be quite up to scratch).

Oh, I read this bit wrong. You probably meant to move the line firing the external script to the change event (and only use the input for updating the text field). That is definitely better for Windows, so I ended up with a bit of logic for each OS (which by the way is part of the Ruby code as I want to hide as much Javascript code as possible).

Yes, exactly.

To speed up evaluation, the platform decisions can be done at load time rather than runtime.

Ie, … defining a method …

if Sketchup.platform == :platform_win
  def method_name
    # MS Windows specific code ...
  end
else
  def method_name
    # MacOS specific code ...
  end
end

The same conditional structure can be used to decide how to define a dialog callback for example. However, since with UI::HtmlDialog class, the callbacks are detached if the user closes them, normally coders put the callbacks inside a method so they can be easily reattached if the dialog is reopened by calling the method again.

1 Like

The first thing I’d try (if the Python library is public) is to check Rubygems.org to see if there is a Ruby port. If so, see if that gem can be easily wrapped into your extension submodule. (There are also threads on using gems with SketchUp here.)


There are threads here discussing the benefits and pitfalls of local servers. They come with their own challenges.

1 Like