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
Dan I totally admire your patience
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:
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
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 ?