PreSave observer is called twice

Hi,
I have attached a AppObserver to my model.
When I export the model to obj sometimes. PreSave in the app observer is called twice.

Console Logs
Exporting C:\Users\Ajay\Documents\Hexagon.skp to obj
Export path: C:/Users/Ajay/AppData/Local/Temp/sentio/Hexagon/Hexagon/Hexagon.obj
Pre save callback
Pre save callback
Error: #<TypeError: reference to deleted DrawingElement>

Even if i press Ctrl+S presave is called twice. Followed by two postsave

Could you show us some code?

This callback is actually documented in the Sketchup::ModelObserver.

Paste this into Ruby Console …

class ModelSpy < Sketchup::ModelObserver
  def self.attach
    Sketchup.active_model.add_observer(self::new)
  end
  def respond_to?(meth)
    puts "SketchUp polled #{self.class} for a #{meth.to_s} callback."
    super(meth)
  end
end

… then execute this …

ModelSpy.attach

Upon saving the model, I see the following in SU2016 … v 16.1.1449

SketchUp polled ModelSpy for a onPreSaveModel callback.
SketchUp polled ModelSpy for a onSaveModel callback.
SketchUp polled ModelSpy for a onPostSaveModel callback.

… so all looks good and normal.


@rajawat What SketchUp version are you seeing this on?
(Your forum profile needs to be updated to show your SketchUp version.)

This might happen if your code has attached an observer twice. (Although in most cases, SketchUp will check to see if the object is already attached, and move it to the end of the callback queue, or ignore the attachment call.)

1 Like

For that reason I asked him to show us some of his code :slight_smile:

There have been double callback call bugs in the past.

But I ran the test again with an OBJ file export and got the same normal expected output.
No double calls.

However, the onSaveModel callback was the old name (version 6) that is still there for backward compatibility.
Going forward you should use only onPostSaveModel callback (version 8+).

If you define both, SketchUp polls your observer object and will call both if they are both defined

Here the code

  module Solutionario
  module Sentio

    REG_KEY = 'Sentio'.freeze
    PLATFORM = (Object::RUBY_PLATFORM =~ /mswin|mingw32/i) ? :windows : ((Object::RUBY_PLATFORM =~ /darwin/i) ? :mac : :other)

    attr_accessor :login_dialog
    attr_accessor :upload_dialog


    def self.display
      initializeAppObserver

      initializeModelObserver

    end

    def self.initializeAppObserver
      puts "Init app observer"
      if @appObserver == nil
        @appObserver = SentioAppObserver.new
        @appObserver.set_open_callback {|model| self.on_open_model(model)}
        @appObserver.set_quit_callback {self.on_quit}
        Sketchup.add_observer(@appObserver)
      end
    end

    def self.initializeModelObserver
      puts "Init model observer"
      @modelObserver = nil
      @modelObserver = SentioModelObserver.new
      @modelObserver.set_post_save_callback { |model| self.onPostSave }
      @modelObserver.set_pre_save_callback { |model| self.onPreSave }
      Sketchup.active_model.add_observer(@modelObserver)
    end

    def self.on_open_model(model)
      puts "Open model"
      if @state.eql? "upload"
        if @upload_dialog != nil
          @upload_dialog.activate
        end
      end
      self.initializeModelObserver
    end

    def self.on_quit
      puts "Quitting sketchup"
      if @upload_dialog != nil
        @upload_dialog.onQuit
      end
      Sketchup.active_model.remove_observer(@modelObserver)
      Sketchup.remove_observer(@appObserver)
    end

    def self.onPreSave
      puts "Pre save callback"
      if @upload_dialog != nil
        @upload_dialog.onPreSave
      end
    end

    def self.onPostSave
      puts "Post save callback"
      if @upload_dialog != nil
        @upload_dialog.onPostSave
      end
    end

    class SentioAppObserver < Sketchup::AppObserver

      def set_open_callback(&block)
        @open_model_callback = block
      end

      def onOpenModel(model)
        puts "Model opened:#{model}"
        @open_model_callback.call(model)
      end #onOpenModel

      def set_quit_callback(&block)
        @quit_callback = block
      end

      def onQuit
        @quit_callback.call
      end #onQuit

    end #class end

    unless file_loaded?(__FILE__)
      menu = UI.menu('Plugins')

      #TODO Group the multiple menu items.
      menu.add_item('Sentio') {
        self.display
      }

      file_loaded(__FILE__)
    end

  end
end
`

Also in PreSave callback i am removing a entity - ComponentInstance - which i have added for user and do not want to save in his/her model.

Steps to reproduce
-Open the plugin twice.
or
Open the plugin then close it and then again open it . Press Ctrl+S

Removing observer when dialog is closed helped me.
How can I have only single instance of plugin running at a time ?

Only load it once.

Observer objects do not need to be instantiated classes. They can be a module. Try this …

module Solutionario
  module Sentio

    extend self

    REG_KEY = 'Sentio'.freeze
    IS_WIN =( Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i )
    IS_OSX = !IS_WIN

    attr_accessor :login_dialog
    attr_accessor :upload_dialog


    def init
      Sketchup.add_observer(self)
    end

    def expectsStartupModelNotifications
      return true
    end

    def onNewModel(model)
      puts "New model"
      if @state.eql? "upload"
        if @upload_dialog != nil
          @upload_dialog.activate
        end
      end
      model.add_observer(self)
    end

    def onOpenModel(model)
      puts "Open model"
      if @state.eql? "upload"
        if @upload_dialog != nil
          @upload_dialog.activate
        end
      end
      model.add_observer(self)
    end

    def onQuit
      puts "Quitting sketchup"
      if @upload_dialog != nil
        @upload_dialog.onQuit
      end
      # You do not really need to do this as SketchUp
      #   will detach all observers during shutdown.
      Sketchup.active_model.remove_observer(self)
      Sketchup.remove_observer(self)
    end

    def onPreSaveModel(model)
      puts "Pre save callback"
      if @upload_dialog != nil
        @upload_dialog.onPreSave
      end
    end

    def onPostSaveModel(model)
      puts "Post save callback"
      if @upload_dialog != nil
        @upload_dialog.onPostSave
      end
    end


    unless file_loaded?(__FILE__)
      menu = UI.menu('Plugins')

      #TODO Group the multiple menu items.
      menu.add_item('Sentio') {
        self.display
      }

      init() # attach the Sentio module as an AppObserver

      file_loaded(__FILE__)
    end

  end
end
1 Like

Thanks @DanRathbun

Didn’t know that. Thanks for sharing.

No problem. If you think about it, a module is an instance of class Module, so it also is actually an instance object.

However, the key here with regard to SketchUp API observers is that they are now all semi-abstract, in that the classes have been made devoid of the actual prototype callbacks, which never did anything anyway. So the core developers decided that it was faster to “duck-type” observer objects by asking them if they respond to a callback before calling that callback.

In addition, the API has never hard typed any observer object by testing if it is descended from any particular observer superclass. This means that you can write more robust hybrid observer classes that have a mixture of callbacks from multiple observers. You don’t even need to declare any superclass for an observer. These hybrid observers often make it easier as the single module or class can share object references that otherwise would need to be passed around as method parameters in a clunky way.

The API observer classes only exist now as a means of documenting the optional callbacks (via YARD.)

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.