Accesing Entity info inside onEraseEntity method

name
ruby
group
observer
entityobserver

#1

I have created a set of groups, and also defined a common Observer for all of them in the following way:

class GroupObserver < Sketchup::InstanceObserver
    def onEraseEntity (group)
       name = group.name
    end
end

group.add_observer ( GroupObserver.new )

When I remove a group, I would like to retrieve the name of the group removed. Debugging I have observed that when entering the method the element is already deleted, so it is not valid. Is it possible to have access to the entity info inside the onEraseEntity method? If not, is there any other way to solve this issue?


#2

Do not put spaces between method names and the opening parenthesis of their parameter list.

No, it is already deleted by the time this callback is called.

Use the #onComponentInstanceRemoved callback of a DefinitionObserver instead, because they fire before callbacks of an EntityObserver object.

(ie, groups are just special kinds of component instances, whose definitions have the #group? flag set true.)


#4

@DanRathbun Thank you for your reply. But, what if I want to check if a page (scene) is removed? I cannot use the #onComponentInstanceRemoved in this case.

My scenario is that I have a pyramid (group of faces) defining a camera pose for a scene, and both page and group have the same name. If I remove the scene I want to remove the pyramid as well, and other way around, if I remove the pyramid the page must be deleted. I want to achieve something like this:

    class PageObserver < Sketchup::EntityObserver
      def onEraseEntity(page)
        group_name = page.name
        # Remove group by its name
      end
    end

    class GroupObserver < Sketchup::InstanceObserver
      def onEraseEntity(group)
        scene_name = group.name
        # Remove scene by its name
      end
    end

    page.add_observer( PageObserver.new )
    group.add_observer( GroupObserver.new )

#5

How about keeping an enduring reference to all pages - as a hash of names in the form:

@@hash={}
Sketchup.active_model.pages.each{|p| @@hash[p]=p.name }
>>> {#<Sketchup::Page:0x000155371e3178>=>"Scene 1", #<Sketchup::Page:0x000155371e3038>=>"Scene 3", ...}

Assemble the @@hash as the model loads and adjust it whenever the model’s pages change at all.
You can compare the current array of model.pages.to_a to your array of @@hash.keys, any elements that are missing from the @@hash you add in as new entries because it’s a new page, any that are in the @@hash.keys but not in the current array have been deleted, so that way you can look up that key’s value [name] in @@hash and delete its group etc as you wish - before removing that key from the @@hash because it’s no longer relevant…


#6

Of course not. You would use PagesObserver#onElementRemoved()

It is problematic to be modifying the model inside observer callback methods, so no guarantees here.
Your scenario needs to be sure not to cause a “vicious loop” (hence the @@active class variable and class attr reader method.)

module Author
  module SomePlugin

    class PagesObserver < Sketchup::PagesObserver
      @@active ||= false
      def self::active?
        @@active ? true : false
      end
      def onElementRemoved(pages, page)
        @@active = true
        # bail out if called by the other observer callback:
        return if GroupDefinitionObserver::active?
        group_name = page.name
        # Remove group by its name
        grp = nil
        pages.model.definitions.each {|cd|
          next if cd.instances.empty?
          grp = cd.instances.find {|i| i.name == group_name }
          break if grp
        }
        if grp
          model.start_operation("Erase Group",true,false,true)
            grp.erase! # will fire the other observer
          model.commit_operation
        end
      rescue => e
        puts e.inspect
        puts e.backtrace if $VERBOSE
      ensure # always do when returning from callback
        @@active = false
      end
    end # class

    class GroupDefinitionObserver < Sketchup::DefinitionObserver
      @@active ||= false
      def self::active?
        @@active ? true : false
      end
      def onComponentInstanceRemoved(definition, group_instance)
        @@active = true
        # bail out if called by the other observer callback:
        return if PagesObserver::active?
        scene_name = group_instance.name
        # Remove scene by its name
        return if definition.model.pages.size.zero?
        page = definition.model.pages.find {|pg| pg.name == scene_name }
        if page
          model.start_operation("Erase Scene",true,false,true)
            page.erase! # will fire the other observer
          model.commit_operation
        end
      rescue => e
        puts e.inspect
        puts e.backtrace if $VERBOSE
      ensure # always do when returning from callback
        @@active = false
      end
    end # class

    # This should be added by an AppObserver:
    Sketchup::active_model.pages.add_observer( PagesObserver.new )

    # This should be added by a DefinitionsObserver:
    group.definition.add_observer( GroupDefinitionObserver.new )

  end
end

#7

That solution won’t work either. onElementRemoved in PagesObserver is working, since the page is still available when entering the method.

However, in GroupDefinitionObserver, the element is already deleted when entering in onComponentInstanceRemoved, so it is not possible to have access to its name.


#8

Well sorry about this. I do not like it either. We’ve complained about these frivolous deletion observer callbacks for years without getting the much needed “onBeforeDelete” callbacks.

So like TIG suggests you have to write your own companion collection of data, and when one of these frivolous deletion callbacks gets called, you then iterate your “companion” collection, checking the object reference key for validity.

result = @@hash.find {|key,val| key.deleted? }
if result.nil?
  # no match so bailout
else # result is an array of [key, val]
  deleted_obj, data = result
  # assuming data is a hash:
  deleted_name = data[name]
end

You could also use OpenStruct for your data object type.


#9

Yes, I finally used that trick proposed by @TIG to solve it, but it would be more straight-forward to have the option with onEraseEntity and onComponentInstanceRemoved.

It seems that the EntitiesObserver can handle it in onElementRemoved, but the EntityObserver cannot.
Thank you guys for your help.


#10

They will not be changed because it would break scripts and extensions “out in the wild.”

The only thing we can hope for is new “onBefore…” callbacks.