How can I get the event when material dropped on Face

How can I get the event and the actual Face where the material was dragged. Also the Material itself…
Thanks

You could use a hybrid observer class, that watches the tool stack (for each model as it is opened,) then when the PaintTool is activated, attaches itself to watch the active entities and the materials collection.

It will not attach to the open model if loaded into the console, so load a new model.
Also, it may need to some tweaking to work on a Mac with multiple models open.

paint_spy.rb (3.2 KB)

which looks like this for those not wishing to d/l the file:


module SomeAuthor
  module SomePlugin
  
    @@loaded ||= false
    @@spy ||= false
    
    def self.spy
      @@spy
    end

    # This is an example of a hybrid observer that watches the tools collection
    # for PaintTool activation, then attaches itself as an obsever to watch the
    # materials collection and the active entities context.
    # It will set instance references to the modified entity and it's material.
    # When the active tool is changed and is no longer the PianTool, then this
    # hybrid observer will stop watching the materials collection and entities.
    class HybridSpy < Sketchup::EntitiesObserver

      # Construction:
      attr_reader( :material_used, :back_material_used, :painted_entity )

      def initialize(*args)
        reset()
        @previous_tool  = nil
      end

      def reset
        @back_material_used = nil
        @material_used  = nil
        @painted_entity = nil
      end

      def watch_tools(model)
        model.tools.add_observer(self)
        puts "Spy now watching Tools collection of active model:\n  #{model.inspect}\n"
      end

      # AppObserver callbacks:
      def expectsStartupModelNotifications
        return true
      end
      def onNewModel(model)
        watch_tools(model)
      end
      def onOpenModel(model)
        watch_tools(model)
      end

      # ToolsObserver callbacks:
      def onActiveToolChanged(tools, tool_name, tool_id)
        model = tools.model
        if tool_id == 21074 # PaintTool
          @previous_tool = 21074
          puts "\nPaintTool activated"
          # Begin watching the materials & active entities collections:
          model.materials.add_observer(self)
          model.active_entities.add_observer(self)
        elsif @previous_tool == 21074
          puts "\nPaintTool deactivated"
          # Stop watching the materials & active entities collections:
          model.materials.remove_observer(self)
          model.active_entities.remove_observer(self)
          @previous_tool = tool_id
          reset() # reset the instance vars to nil
        end
      end

      # EntitiesObserver callbacks:
      def onElementModified(entities, entity)
        return unless entity.is_a?(Sketchup::Drawingelement)
        puts "\nonElementModified: #{entity}"
        @painted_entity = entity
        if entity.respond_to?(:material)
          puts "  front material: #{entity.material.inspect}"
          puts "  material name : #{entity.material.name}" unless entity.material.nil?
          @material_used = entity.material
        else
          @material_used = nil
        end
        if entity.respond_to?(:back_material)
          puts "  back material : #{entity.back_material.inspect}"
          puts "  material name : #{entity.back_material.name}" unless entity.back_material.nil?
          @back_material_used = entity.material
        else
          @back_material_used = nil
        end
        #
        ### Notify some other code object of paint action ?
        #
      end

    end # class HybridSpy

    if !@@loaded
      # Attach as an AppObserver:
      @@spy = HybridSpy.new
      Sketchup.add_observer(@@spy)
      @@loaded = true
    end

  end
end
3 Likes

Dan I totally admire your patience :slight_smile:
Thanks for the example.
This event also need a Material method, like MaterialObserver.onMaterialAssigned or something.

There is:

Sketchup::MaterialsObserver#onMaterialRefChange()

… that can be used. I’ll let you take it from here, and add any more functionality you need in that regard.

There was an error in the onElementModified() callback method:

@painted_entity = entity

was this (in error):

@entity = entity

(The above example and file has been fixed.)

Yes I know about that, but it doesnt tell me on which face it was dropped on.

The painted_entity() method (created by the attr_reader call,) will return the current object that @painted_entity is pointing at.

Did you miss what I said ? Ie:


BUT, I found another silly mistake on line 69.

Drawingelement needs to be Sketchup::Drawingelement

(I fixed the above file, and posted example.)


After fixing, it works fine for me:

This is the console output when painting a face with a transparent textured material:

onElementModified: #<Sketchup::Face:0x000000088f9690>
  front material: #<Sketchup::Material:0x000000086bb6d0>
  material name : [Translucent Glass Tinted]
  back material : #<Sketchup::Material:0x000000086bb6d0>
  material name : [Translucent Glass Tinted]

… and painting a front face with a non-transparent textured material:

onElementModified: #<Sketchup::Face:0x0000000ec14e50>
  front material: #<Sketchup::Material:0x0000000e303de8>
  material name : [Carpet Berber Multi]
  back material : nil

… and painting a back face with a non-transparent material:

onElementModified: #<Sketchup::Face:0x0000000e18fa98>
  front material: nil
  back material : #<Sketchup::Material:0x0000000de8a168>
  material name : [Roofing Tile Spanish]

… and painting a group with a non-transparent textured material:

onElementModified: #<Sketchup::Group:0x0000000e301188>
  front material: #<Sketchup::Material:0x0000000df622e8>
  material name : [Roofing Scalloped]

Now you see the line that says:

### Notify some other code object of paint action ?

… that is where you would call some method in your other code (class instance or other module,) to either pass the references to the new entity, and it’s materials, ie:

OtherModule::notify_material_change(
  @painted_entity, @material_used, @back_material_used 
)

And I using SketchUp 16.1.1449.

@nekitu, I cannot tell what you are doing wrong unless you post the code you are having issues with.

Yeah, it was a mistake of mine, I was modifying the picked entity inside an observer call, and that seems to be a no go, since any modification of the objects will trigger another call, afaik. Is that right ? Any easy way to disable observers from a list while doing entity modifications? and hopefully they will not be called after being enabled with that modification. Or a better idea is to gather the picked entities in a global list when observer is called, and in a timer to pick them up and modify them (I need to set some attributes).

Yes don’t that. It can cause a crash.

There is an example in the SketchUp Team’s GitHub site:

1 Like

Thanks a lot Dan, it seems the sample is the same as I proposed, timer stuff with cached refs, this observer “inception” should be addressed in the future, because it has simpler solutions than the current nightmare.

Tried that safe observer events, but it doesnt work in SU 16.1.1449 x64. Just pasted the example in a new file and require included the utility.

require 'safer_observer_events.rb'

class MySaferEntitiesObserver < Sketchup::EntitiesObserver

  include SaferObserverEvents

  def safer_onElementAdded(entities, entity)
	puts entity.to_s
  end

end # class

observer = MySaferEntitiesObserver.new
Sketchup.active_model.entities.add_observer(observer)

This is good question for @tt_su (Thomas).

I’m late to this conversation. The original question appear to me marked as resolved. What is the current question?

Oh great, please add an observer method in MaterialsObserver:
onMaterialAssigned(material, entities, entity) or something similar, for when a material dropped on some entity (Face, ComponentInstance, Group etc)

would make life easier for me and probably more people using the Ruby API :slight_smile:
the onMaterialRefChanged doesnt help me a bit.

it was:

Which is sort of off-topic. I suppose it should be broken off into a “safe observer events” thread of it’s own.

The feature requests in post 16 are relevant, as this whole thread is about writing your own onMaterialAssigned functionality, as I noted at the bottom of post 7:[quote=“DanRathbun, post:7, topic:33032”]
Now you see the line that says:

### Notify some other code object of paint action ?

… that is where you would call some method in your other code (class instance or other module,) to either pass the references to the new entity, and it’s materials, ie:

OtherModule::notify_material_change(
@painted_entity, @material_used, @back_material_used
)

[/quote]

hm… yea, the SaferObservers example was made before SU2016. It’s not needed for SU2016+ - but if you want to support across these version then that is an issue. hm… surprised this is the first time I hear about that.

Yea, that event - I still don’t understand its purpose. It was added long before my time - so history is lost on me.

I think you should get an onEntityChanged event if you apply a material - using the current observers.

It signals a material usage change at the materials collection level, for a particular material. This would be useful to “takeoff” type extensions that would need to say, recalculate square area used for tiles or wood flooring, etc.

It actually works better using the Sketchup::EntitiesObserver#onElementModified() callback, as the OP does not know what object will be painted, and really doesn’t wish to watch every face, group and instance (at every entities level.)

See the example above, which is actually a nifty little test example, that could be massaged a bit and added to a test suite, perhaps sketchup-developer-tools ?