Extension does not work on Mac with multiple models open

Hello everyone!

This is something I’ve noticed for a long time, but never had the desire to fix until now. My extension (which has grown a little bit over time), doesn’t work at all when opening a second SU model on macOS. I thought that Sketchup.active_model would be able to select the appropriate model in this scenario, but it looks like it doesn’t. Worse, none of my context menus are showing up either, so the standard way of invoking the API doesn’t seem to produce the expected results.

Not sure how to fix this, or if this is possible to fix using the current API. I couldn’t find any code snippet on this matter, so I’m reaching out here for your ideas.

Thank-you in advance!

Make sure your code runs every time the user activates it, and it sets
model = Sketchup.active_model
etc at that point in time.
If you set it so that model is set up as the extension loads it relates to the model you had open at that time.
On a PC that’s OK because each SketchUp activation is self-contained - one per model, but on a MAC there’s only one SketchUp active, with multiple models associated with it.

3 Likes

Reinforcing @TIG ‘s point, on a Mac you should never assume that the active model stays the same between one tool callback and another. The user can have any number of models open and can switch to another one whenever they get control of the GUI. Always start with #active_model in every callback.

2 Likes

Thanks guys!

Just to clarify for people visiting this thread down the road. The following will not work for multiple models because @su_model will only point to the first model:

class EntryPoint
  def initialize
    # Instance variable.
    @su_model = Sketchup.active_model
    init_context_menu
  end

  def init_context_menu
    UI.add_context_menu_handler do |context_menu|
      context_menu.add_item('Make red') {
        @su_model.start_operation('Make red', true)

        entities = @su_model.entities

        entities.grep(Sketchup::Edge).each do |e|
          e.material = Sketchup::Color.new(255, 0, 0)
        end

        @su_model.commit_operation
        @su_model.selection.clear
      }
    end
  end
end

unless file_loaded?(__FILE__)
  EntryPoint.new
  file_loaded(__FILE__)
end

Instead of creating the instance variable (which is what I did throughout my codebase), one has to make it a local variable like this:

class EntryPoint
  def initialize
    init_context_menu
  end

  def init_context_menu
    UI.add_context_menu_handler do |context_menu|
      context_menu.add_item('Make red') {
        # Local variable.
        su_model = Sketchup.active_model
        su_model.start_operation('Make red', true)

        entities = su_model.entities

        entities.grep(Sketchup::Edge).each do |e|
          e.material = Sketchup::Color.new(255, 0, 0)
        end

        su_model.commit_operation
        su_model.selection.clear
      }
    end
  end
end

The first question I have for you is: Why is this wrapped up in a class object?

Second is: Why are you clearing the active model’s selection, when the code is not previously even using the selection? End users are not found of this happening unless it is expected & intuitive behavior.

It was just an example, Dan. In my actual code I keep track of some state and use a bunch of instance variables as a result. And yes, I meant to call su_model.selection and iterate over those items. I find it quite intuitive to have the selection cleared at the end.

Well, this is fine and dandy, but context menu handlers are application-level objects, not per model objects. It does not really make much sense to have its definition wrapped up within an instance method.

It is more normal to have them within a load guard block so that they will only be defined once when the extension submodule first loads. This prevents duplicate commands appearing on the context menu.

1 Like

There is no context menu duplication with my approach either. I prefer having my entire entry point in a class as opposed to standalone functions living in the extension namespace.

A class is a code object that will be instantiated multiple times, usually holding instance level state.

A module is a code object, (class Module being the superclass of class Class,) used where there will only ever be one instance.