SelectionObserver is not being fired

Hi there,

I have an extension where we create our products(ComponentInstance) from a UI::HtmlDialog. UI::HtmlDialog has images, on clicking the image we create a 3D model. This UI::HtmlDialog also has tabs. One of the tabs is edit, which shows properties on the selected 3D model. The user can update these values, which will reflect in the 3D model.
I have a SelectionObserver which I attach to selection like this Sketchup.active_model.selection.add_observer whenever a new product is created or loaded from a saved file.
During the update operation I mentioned above(From UI::HtmlDialog)

  1. I remove the observer using Sketchup.active_model.selection.remove_observer
  2. Update the 3D model
  3. I add back the observer Sketchup.active_model.selection.add_observer

Note: I only maintain only one observer instance

    def remove_product_selection_observer
      Sketchup.active_model.selection.remove_observer(@@product_selection_observer)
    end

    def add_product_selection_observer
      if(@@product_selection_observer == nil)
        @@product_selection_observer = ProductSelectionObserver.new
      end
      Sketchup.active_model.selection.add_observer(@@product_selection_observer)
    end

Use case:

  1. Create a 3D model from UI::HtmlDialog. Call it ‘product 1’
  2. Create another 3D model from UI::HtmlDialog. Call it ‘product 2’
  3. Now switch the tab in UI::HtmlDialog to edit(edits ‘product 2’). Update some properties.
    Point 3 code basically looks like this
        product.remove_product_selection_observer
        product.update(updated_properties)
        product.add_product_selection_observer

Now when I select ‘product 1’ none of the methods in the observer gets fired. I rely on these observer events to figure out that a different product is selected and based on that I send the selected product properties to UI::HtmlDialog.
Q: Why the selectionObserver method is not getting triggered?
Is there any better way to debug than just making sure the observer is attached and the observer’s methods are fired?

P.S: After point 2 in the use case, if I double click on my product(componentInstance), I start getting observer events fired correctly.
How can I debug this further to figure out why my observer is not firing events.

Selecting with the Select Tool or via the Ruby API?

How are you creating new 3D models from UI::HtmlDialog?

Can you show a complete minimal example? I would make it a lot easier to follow along when one can paste some demo code into the Ruby Console.

Via the select tool

The UI::HtmlDialog basically sends some json which we use to create a product.

I am working on a text extension to demonstrate.

1 Like

:+1:

Also, do you see this on Windows or Mac? Or both? (And what SketchUp version?)

You are on Mac. Is “product” a Sketchup::ComponentDefinition object, or a Sketchup::Model object in it’s own document window ? (Ie, do you have 2 document windows open, one with “product1” and the other with “product2” ?)

1 Like

I am seeing this on both mac and windows on versions 2020, 2021, and 2021.1

I am attaching the sample extension. But it is working as expected in the sample extension :frowning: . The only differenece is that we are using a react app for htmldialog and all JS function that SketchUp calls are on global.

test.zip (6.2 KB)

I tried attaching a screen recording showing this but its size is big for the forum. Here is a link you can see.

Product is a ComponentInstance. No, I do not have two separate windows/models

One wired behavior is:

in the following workflow

after updating some value in HTML(after which the 3D model updates). Now if I double click on any product, then the observer starts working. This is a little hard to explain I will see if I can share a screen recording of the actual product.

It’ll be impossible for us to debug this without being able to reproduce. Could you dig further to identify the difference between the simple sample that works and your real world extension that doesn’t?

I don’t know where to begin to debug. Ok let me explain it like this:

I have a SelectionObserver which listens to selection changes. I have routines for:
onSelectionAdded
onSelectionBulkChange
onSelectionCleared
When any of the above triggers I send some properties to my HtmlDialog.

Now

  1. I create a product(say product1)
  2. I create another product(say product2)
  3. Now If I switch between products i.e change the selection, I get events from the observer and I send the properties to HTML to show the right properties based on the selection.
  4. If I update the product2 from my properties window the 3D model gets updated(I again make a call to HTML dialog to send the updated properties).
  5. Now if I change the selection between the product1 and product2 none of the observer method gets triggered.

Important
6. Now if I

  • double click on say product1 and select anywhere on the model again. And then change the selection to product1 or product2 the observer events gets fired.
    or

  • if i draw a rectangle on the model(using Sketchup rectangle tool). And then change the selection to product1 or product2 the observer events gets fired.

What could be the reason that double click or drawing the rectangle make the Observer work again.

Given this, where can I debug to see what out update method is doing that makes the Observer stops working.
cc: @tt_su @DanRathbun

A few observations about the test code …


(1) The extensions registrar script is not in the correct namespace or submodule as the test extension.
The module nesting you used …

module Examples
  module HelloCube

… is specifically for the “Hello Cube” example in the SketchUp Team’s Examples repository.

YOU cannot use that Examples namespace for YOUR extensions (even for testing.)

ALL of your extensions, whether they are test or production MUST be within YOUR top level namespace module.

(2) Your test observer module is also not within YOUR top level namespace module.
Ie, it should be something like:

module AnuragJoshi
  module TestSelectionObserver

We do not care what your too level namespace is as long as it is unique.


(3) Your observer is using an attribute dictionary named "properties".

The model space (database) is a shared space by ALL extensions. This means that you MUST prepend your extension’s attribute dictionaries with something unique. (We all cannot be using a name as simple as "properties". There will be clashes that will cause errors in your or other devs plugins.)

Just as you must put the extension files in a uniquely named subfolder and wrap the extension code in uniquely named nested modules, the extension’s attribute dictionary should have your extension’s unique name as a prefix.

If the extension module was nested as AnuragJoshi::TestSelectionObserver then it’s extension subfolder should be named "AnuragJoshi_TestSelectionObserver" and it’s attribute dictionaries should have the same name (or use "AnuragJoshi_TestSelectionObserver" as a prefix, like "AnuragJoshi_TestSelectionObserver_properties".)

Since an extension will likely use it’s own dictionary name many places, it is normal to define an extension’s dictionary name with a constant at the top of the extension submodule …

    DICT ||= 'AnuragJoshi_TestSelectionObserver_properties'

… then use that constant whenever you need to use any of the dictionary methods.

      # Test an object for the extension dictionary.
      def self.has_dictionary?(entity)
        entity.respond_to?(:attribute_dictionary) &&
        entity.attribute_dictionary(DICT)
      end

      # Get a product id:
      def self.get_product_id(entity)
        return nil unless self.has_dictionary?(entity)
        entity.get_attribute(DICT, 'id', nil)
      end

… and so forth, and so on …


(4) There is little (if any) comments in the code explaining what it is attempting to do.

This is a big deal. If you want help with your code … comment it !

After you read number (5) below, that and no commenting really means I (and others) are unlikely to want to really care to find out why your code has problems.


(5) Your use of the SelectionObserver is convoluted and overly complex which is resulting in hard to understand and debug code.

Simplify it.

Attach the observer to the active model’s selection when your edit dialog first opens.
And remove the observer when your edit dialog closes.

Whenever there is a change in the selection, your observer’s callbacks determine if the selection is a single_object? and if that object is one of YOUR components (by checking an attribute, a persistent_id or a guid, etc.)

If it is not an object that your observer is “watching” then your observer callback should simply return without doing anything. (Ie, it ignores what it does not care about.)

If your code also has some mode that you do not want to do something in your observer callbacks, then you set a state variable (ie … @edit_mode = true,) and test for that mode as a “bail out” condition in your observer callbacks.

But doing this convoluted attach / detach / reattach observer pattern is the root of your problems.


Lastly, I think there was (or still is) a bug in the SelectionObserver in that sometimes the SketchUp engine polls for an onSelectedAdded callback (in error) instead of the onSelectionAdded callback (as documented.)
So you may need to alias your onSelectionAdded callback like so …

alias_method(onSelectedAdded, onSelectionAdded)

You can test for what named callbacks that the SketchUp engine is polling for by adding the following to your observer classes (of any kind) …

    def respond_to?(callback)
      puts "SketchUp is polling for a callback method named: #{callback}"
      super
    end

… and have the Ruby Console open during testing.

NOTE: Only override respond_to? during testing as it will slow down your observer.
(IO can be slow.)

I was able to solve the issue by adding Sketchup.active_model.commit_operation at the end of the update(This is a bandaid fix for our extension, we will research how the transaction system works when we will work on undo/redo feature and have the ideal fix(if it exists)).

Findings: If some transaction is still running(not committed) then the Selection observer will not fire events.

Have you read the Observer overhaul document for the SU2016 release ?

1 Like

No @DanRathbun I haven’t. Thanks for the link.

Ah, yes - that is by design. As allowing observers to fire in the middle of operations was a major source of crashes and bugs. Observers could erase or modify the state of the model while the operation was working causing all kinds of havoc. As of SU2016 they are queued up until there is no operation active.

2 Likes