How to observe multiple element is added to the Sketchup::Entities collection?


#1

The api only observe a single element is added to the Sketchup::Entities collection.

But How to observe multiple element is added to the Sketchup::Entities collection?

I write a code to explain the issue.

model = Sketchup.active_model

# create two lines
line1 = model.entities.add_line [0,0,0], [10,5,0]
line2 = model.entities.add_line [10,5,0], [10,10,0]

# puts the persistent id of lines
puts line1.persistent_id, line2.persistent_id # => 9036, 9039

# earse one line
model.start_operation('erase line1', true)
model.entities.erase_entities(line1)
model.commit_operation

# earse another line, append operation to the previous operation
model.start_operation('erase line2', true, false, true)
model.entities.erase_entities(line2)
model.commit_operation

# bind a observer and puts the the persistent id of new entity.
class IssueObserver < Sketchup::EntitiesObserver
  def onElementAdded(entities, entity)
    # FIXME: only show the undo earse line1 operation
    p entity.persistent_id # => 9036
  end
end
model.entities.add_observer IssueObserver.new

# undo the earse operation
Sketchup.undo

#2

Do I see it right that the code sample adds the observer after the entities have been created?
Then it won’t be notified about these entities (maybe an onElementRemoved or onEraseEntities would be notified).


#3

@Aerilius, I add the observer after the entities have been create, because I want the undo operation to re-create the two lines.

expect:

create two lines -> earse two lines -> undo earse operation -> observe two lines create

actual:

create two lines -> earse two lines -> undo earse operation -> observe one lines create


#4

Try adding it before the objects are created and see how it works then.


#5

Use a combo of ModelObserver and EntitiesObserver. Listen to when onStartTransaction and corresponding commit/abort triggers. Any entity you trigger in between you can then act upon in bulk.


#6

Thanks.
This is a temporary solution.
But, If a new element is added, all the elements need to be traversed and checked
It shouldn’t be like this.


#7

I can’t watch the observer to find the attribute of the deleted entity .

...
def onComponentInstanceRemoved(definition, instance)
    # TODO: do something, just print the the id of test_dict here
    puts instance.get_attribute('test_dict', 'id')
end
...

entities = Sketchup.active_model.entities
test_group = entities.add_group
test_group.set_attribute 'test_dict', 'id', 123
entities.erase_entities(test_group)

This is a sadly thing for me.
So I have to write a lot of hack codes to improve the api.
In the process, I encountered more problems about the ruby api.
Then I have to write move codes to fix the problems.

# open Entity class and cache attributes.
class Sketchup::Entity
  # initialize attributes from attribute_dictionaries
  def init_attribute
    @dicts = {}
    unless self.attribute_dictionaries.nil?
      self.attribute_dictionaries.each do |attribute_dictionary|
        dict = {}
        attribute_dictionary.each { |key, value| dict[key] = value }
        @dicts[attribute_dictionary.name] = dict
      end
    end
  end

  alias_method :origin_get_attribute, :get_attribute
  alias_method :origin_set_attribute, :set_attribute

  def get_attribute(dict_name, key, default_value = nil)
    if self.deleted?
      if !@dicts.nil?
        dict = @dicts[dict_name]
        if !dict.nil? && dict.has_key?(key)
          dict[key]
        else
          default_value
        end
      end
    else
      origin_get_attribute(dict_name, key, default_value = nil)
    end
  end

  def set_attribute(dict_name, key, value)
    @dicts ||= {}
    @dicts[dict_name] ||= {}
    @dicts[dict_name][key] = value
    origin_set_attribute(dict_name, key, value)
  end
end

class Sketchup::ComponentDefinition
  # initialize attributes of instances
  def init_attribute
    return if self.deleted?
    self.instances.each(&:init_attribute)
  end
end

class Sketchup::DefinitionList
  # initialize attributes of definitions
  def init_attribute
    return if self.deleted?
    self.each(&:init_attribute)
  end
end

# initialize attributes when new component instance define
class HackAttrDefinitionObserver < Sketchup::DefinitionObserver
  def onComponentInstanceAdded(definition, instance)
    instance.init_attribute
  end

  def onComponentInstanceRemoved(definition, instance)
    # TODO: do something, just print the the id of test_dict here
    puts instance.get_attribute('test_dict', 'id')
  end
end

class HackAttrDefinitionsObserver < Sketchup::DefinitionsObserver
  def initialize(model)
    @observer = HackAttrDefinitionObserver.new
    model.definitions.init_attribute
    model.definitions.each { |definition| definition.add_observer @observer }
  end

  def onComponentAdded(definitions, definition)
    return if definition.deleted?
    definition.init_attribute
    definition.add_observer @observer
  end
end

# undo and redo may cause attributes error
class HackAttrModelObserver < Sketchup::ModelObserver
  def init_attribute(model)
    model.definitions.init_attribute
  end

  alias_method :onTransactionUndo, :init_attribute
  alias_method :onTransactionRedo, :init_attribute
end

class HackAttrAppObserver < Sketchup::AppObserver
  def initialize
    bind_observers(Sketchup.active_model)
  end

  def bind_observers(model)
    model.add_observer HackAttrModelObserver.new
    model.definitions.add_observer HackAttrDefinitionsObserver.new(model)
  end

  alias_method :onNewModel, :bind_observers
  alias_method :onOpenModel, :bind_observers
  alias_method :onActivateModel, :bind_observers
end

unless Object.const_defined? 'HACK_ATTR_APP_OBSERVER'
  HACK_ATTR_APP_OBSERVER = HackAttrAppObserver.new
  Sketchup.add_observer HACK_ATTR_APP_OBSERVER
end

## TEST CODE

entities = Sketchup.active_model.entities
test_group = entities.add_group
test_group.set_attribute 'test_dict', 'id', 123
entities.erase_entities(test_group)

So I hope the SU to impove the api,the next version(SU 2019)

  • can get the info of delete entity(not the enttiyId)
  • element change by the undo and redo operation can observe

BTW, Revit can read the delete info directly.

Hope to be more and more friendly to developers soon


#8

I hope this code you show is only for your internal purpose.
It is not allowable to release public code that modifies either Ruby Core classes or SketchUp API classes. This is a shared environment we all use.

You should look at using refinements …


#9
  1. I hope that the code is not using after the next version(SU 2019).
  2. The code is only to show the issue, it’s just a hack code, it’s should not use in production env, because of no test.
  3. You are right, we don’t modify core classes or API classes.
  4. Thanks.