Extension web interface

My extension gets a bug splat when using Windows SketchUp Pro 2022. I am using web page interface. Was something deprecated? My link is using “skp:selection@”. Any ideas where to begin.

Without seeing your code snippets … it is difficult.

However you should be using the newer UI::HtmlDialog class.

This is the old skp: protocol used with the old deprecated class. The new class does not need to use this protocol as there is a sketchup JavaScript object defined in the new class’ Chromium environment.

2 Likes

UI::WebDialog is deprecated, but no functionality removed. Even if something was removed it should not be allowed to crash SketchUp.

A reproduction code snippet would be useful.

AllanBlock_3DModelingTools.rb (24.5 KB)

AllanBlock_3DModelingTools.rb (24.5 KB)

Here is my code that gives the bug splat.

Several issues with your Ruby coding.

1. Ruby uses 2 space indents.

2. An extension should only create it’s commands and toolbar once.
You have the creation code inside a menu item block. This means that each time the menu item is clicked, that extra duplicate commands and toolbar will be created.

3. Your “Load” menu item is attempting (poorly) to create a similar load on demand that is built into SketchUp if a proper SketchupExtension object is created in a registrar script and the extension code is placed in an extension subfolder.
This allows the user to switch the extension on and off via the Extension Manager interface.

4. Commands and menu items cannot be dynamically redefined. For this reason it is better to wrap command code within a Ruby method and have only that method call within the menu item or command block.
Then during development, the coder can reload their extension code (or a certain part if it’s separated into multiple files,) … without having to close and restart SketchUp.

5. Try to leverage the global __dir__ and File.join methods, as well as temporary references to reduce the use of long literal path strings:

      # Before all the command definitions ...
      icons = File.join(__dir__,'icons')

      # later below when defining command objects ...
      cmdf.small_icon = File.join(icons, 'btnFavorites-16.png')
      cmdf.large_icon = File.join(icons, 'btnFavorites-24.png')

The above assumes you setup an extension properly with the code files within a subfolder. (Only the registrar file goes in the “Plugins” folder.)

6. It is not necessary to set the toolbar reference multiple times. IE …

      toolbar = toolbar.add_item cmds

… etc. It is already set when you called the class constructor ::new method.

7. Honor the user’s toolbar and interface settings …

toolbar.get_last_state == TB_NEVER_SHOWN ? toolbar.show : toolbar.restore

8. If you must use file_loaded? then the call to file_loaded( __FILE__ ) should go within the file_loaded? conditional block.
We’ve long advocated that instead of using these methods to compare very long path strings (which is slow) in a very large global array, that coders should use a module variable within their extension submodule to wrap conditional code such as GUI object creation. Ex:

      unless defined?(@gui_loaded)
        #
        # ... menu and toolbar creation here ...
        #
        @gui_loaded = true
      end

9. Normally callbacks are attached to the dialog object before the window is shown. Also you should define the callbacks in a method. If the user closes the dialog window, the object still exists but the callbacks are disconnected. Having the callback definitions within a method allows them to be reconnected by calling the method again if the dialog is reshown.

10. As noted (in 5.) above about using __dir__, if you have resources in subfolders of your extension subfolder, you can use …

more = File.join(__dir__, 'Textures', 'More')

… and use this reference to load files.
The reason is that Sketchup.find_support_file() looks in multiple (hardcoded) places (that can vary between Mac and Windows) and could return false or an empty string which you do not want passed as an argument to the global require method.
The places where it searches has not been made a part of the API contract. (The doc says “within installation directory” but this may not be 100% true anymore.)
So, anyway, you know where they are so there is no need to search for them.

11. Code defensively when loading external files or doing other IO.
Verify that files exist before calling require or load, and verify the return values from require or load. Don’t assume everything went okay.
BTW, require will only load a file once and return true, otherwise it does not and returns false.
Same goes for any use of Sketchup.find_support_file(), it’s return needs to be checked for “truth”.

12. With the new UI::HtmlDialog class, you no longer have to pass everything as a string and split it up afterward.
Instead you can pass JavaScript arrays and objects to the Ruby callbacks, and they will convert them to Ruby Arrays and Ruby Hashes, respectively.

That’s unfortunately not a complete example that can be run in isolation. It’s not possible to just look at the code and determine what might crash on your system. Can you produce a complete minimal example that we can run on our machine please?

1 Like

I found the line of code that is producing the bug splat in SketchUp Pro 2022 windows.

Within a dialog.add_action_callback I use UI.openpanel(“Open Ruby Script File”, “c:/downloads”, “*.rb”) to open a ruby file. The UI.openpanel is the offending code. I even replaced it with the example code from your website and get the same error. chosen_image = UI.openpanel(“Open SKP File”, “c:/”, “model.skp”)

Any ideas why this is not working in version 2022?

UI.openpanel is a modal dialog which causes the callback proc to be synchronous, ie sequential code.
But the UI::HtmlDialog’s callbacks are supposed to be asynchronous. It is best if the callback proc returns immediately.

The solution is likely to wrap the UI.openpanel call in another method, and call that method from the callback proc from inside a UI.start_timer block (even if it has a zero wait time.)

dialog.add_action_callback do |x, param|
  UI.timer(0,false) { do_open_panel() }
end

def do_open_panel
  path = UI.openpanel(
    "Open Ruby Script File", File.join(ENV["USERPROFILE"],"Downloads"), "*.rb"
  )
  require path if path
end
1 Like

I think that is similar issue as with UI::HtmlDialog: another bug on Mac - Cannot launch a modal dialog from another dialog · Issue #526 · SketchUp/api-issue-tracker · GitHub

Starting something modal in the HtmlDialog callbacks appear to trigger a crash.

1 Like

I found the using the timer fixed the bug splat but the timer never stops executing the code. If I set the timer for 1 sec it opens a new open panel every second. I set the timer to only run once but it’s line the energizer bunny “it keeps on going”. UI.start_timer(1,false) is set too false. Any other ideas?

Oh yes, this is a known bug!

UI .start_timer-class_method
Note that there is a bug that if you open a modal window in a non-repeating timer the timer will repeat until the window is closed.

:bulb: Maybe this hack can work on Mac too, at least it works in Windows.

my_timer_id = UI.start_timer(1, false) { 
  UI.openpanel("Open SKP File", "c:/", "model.skp")
}
UI.start_timer(1.1, false) { UI.stop_timer( my_timer_id )}
1 Like

Wow, thats sad but good to know!

Do you guys know if it applies to any modal inside HtmlDialog callback? such messagebox too?

I use both openpanel and messagebox inside callback and never got any error (SU18 on Win10/11)
Even so thanks yall for getting this workaround, gonna apply it.

Also looking for a confirmation if works on mac.

Yes. A modal window is a modal window regardless of what is within it. This also means UI.inputbox.

2 Likes

@dezmo script works in Mac too

2 Likes

It’s possible to work around that:

1 Like

I have the timer code working. Thank you.

I am now having problems with issue 10.

I would like to update this code.

require Sketchup.find_support_file(‘VegetationGrass.rb’, ‘Plugins/AllanBlock_RetainingWall_3DModelingTools/Textures/More’)

I have added: more = File.join(dir, ‘Textures’, ‘More’) but I can’t seem to find the syntax to combine it with the Sketchup.find_support_file.

We are kind of getting off the topic here … or is there even a clear topic trend to this thread ?


Please post code correctly on the forum. For short one-liners you can use backtick delimiters, ie ` (it’s the key on the left above the TAB key and to the left of the 1 key.)


Once again, you DO NOT use Sketchup.find_support_file to find YOUR OWN extension’s files.

So lets us say within a method in a rb file, evaluated from your extension subdirectory, you have the statement …

more = File.join(__dir__, "Textures", "More")

… then the string that more references should be :
".../Plugins/AllanBlock_RetainingWall_3DModelingTools/Textures/More"
(where the .../Plugins is the absolute path to the "Plugins" directory for whatever version of SketchUp that is running.

In other words, the global __dir__ method returns the absolute path to the directory of the rb file that it is called from.

Got it?

But if you extension is going to use this path many times, you might as well defined either a @more var or a local constant within your extension submodule.

So from then on you reuse that path string like …

veggy_grass = File.join(@more, "VegetationGrass.rb")
load veggy_grass

Also if you are going to use the "VegetationGrass.rb" multiple times, then you must use load() as require() will only load and run it once per session.

Ie, it looks like perhaps your material files might be sequential command-like scripts. But as I don’t know really what you’re trying to do, I cannot say for sure.

It just looks a bit weird what you’re doing from the outside. Most developers would never have a standalone rb file for individual materials. A single Ruby method can be coded to create any material from a short list of parameters.

def create_material(matl_name, collection)
  textures = File.join(__dir__, "Textures", collection)
  fail(IOError,"Bad folder name.",caller) if !File.exist?(textures)
  texture = File.join(textures, "#{matl_name}.png")
  fail(IOError,"Bad texture name.",caller) if !File.exist?(texture)
  matls = Sketchup.active_model.materials
  material = matls.add(matl_name)
  if material
    material.texture= texture
    # set other properties, etc. ...
  end
  return material
rescue => error
  # Handle errors here ...
end

And then from elsewhere you call the method …

material = create_material("Vegetation Grass", "More")
if material
  # always check for truthy after creating SketchUp resources as
  # they can fail and be nil (falsey). Use it here if it's truthy ...
end
1 Like

Did you see Sketchup.find_support_file used in an example somewhere? I’ve been trying to remove it’s usage from examples looking up files in an extension bundle, but I suspect there’s plenty around still.