I have a plugin that makes changes to component transformations and materials when the scene is changed. If I run the update method from the Ruby console it works as expected - changing transformations and materials. However, I can’t figure out why it only changes transformations using the SceneObserver frameChange method.
Any help would be greatly appreciated. I’ve included the relevant code below.
class SceneObserver
def initialize
@curr_page = nil
end
def frameChange(from_page, to_page, percentage)
if percentage == 1.0 and to_page != @curr_page
@curr_page = to_page
MyModule.restore_data to_page
end
end
end
Module MyModule
def self.initialize
Sketchup::Pages.add_frame_change_observer(SceneObserver.new)
end
def self.restore_data(page)
materials = Sketchup.active_model.materials
page.get_attributes('MyModule', 'Data').each { |guid, trans, color|
ent = find_component guid
next if ent.nil?
ent.transformation = Geom::Transformation.new trans
ent.material = materials.add guid if not color.nil? and ent.material.nil?
ent.material = color.nil? ? nil : materials.find { |m| m.name == color }
}
end
def self.find_component(guid)
Sketchup.active_model.entities.find { |e|
is_component?(e) and e.guid == guid
}
end
end
It’s hard to say, since the fragment you posted is incomplete.
One thing that I assume is a typo in transcribing the code to your post: there is no #get_attributes method on Page, only #get_attribute.
When is the ‘MyModule’ dictionary added to the Page, and what does its ‘Data’ attribute look like? Clearly it must be something that provides #each, such as an Array. And each element must be a triple of values, such as a 3-element Array. But beyond that I have no idea how the elements were loaded and whether they are always valid.
The line
ent.material = materials.add guid if not color.nil? and ent.material.nil?
seems odd. Could it be that the if statement causes this to be skipped when you run from the Console, but to fail when you run otherwise?
Your find by guid method is doing more work that it need to. Ie, it is checking all entities.
It could be more efficient like:
def self.find_component(guid)
Sketchup.active_model.find_entity_by_id(guid)
rescue NoMethodError # for older than v 15
Sketchup.active_model.entities.grep(Sketchup::ComponentInstance).find {|e|
e.guid == guid
}
end
Thanks for your replies. I’m quite frustrated with this and apologize for not making the problem clearer.
I’ve disabled my extension and then created a bare minimum plugin to only test color changes in the frameChange method. I’ve included the full test plugin code at end of this reply.
If I select a component in the model and run the following from the Ruby Console:
Test.change_color
Then the console outputs:
change_color start
#<Sketchup::Material:0x0000000048c128>
change_color end
With the component still selected and then change to another scene, then the console outputs:
frameChange start
change_color start
change_color end
frameChange end
Note that the material is NOT output between the change_color start/end. Indicating that the material is nil. So, for some reason, material changes are not workng inside the frameChange method.
Again. Thanks for your input.
Test.rb
require 'sketchup.rb'
module Test
def self.initialize
Sketchup::Pages.add_frame_change_observer(SceneObserver.new)
end
def self.change_color
puts 'change_color start'
c = Sketchup.active_model.selection.first
c.material = '[0020_Red]'
puts c.material unless c.material.nil?
puts 'change_color end'
end
class SceneObserver
def initialize
@curr_page = nil
end
def frameChange(from_page, to_page, percentage)
if percentage == 1.0 and to_page != @curr_page
@curr_page = to_page
puts 'frameChange start'
Test.change_color
puts 'frameChange end'
end
end
end
end
unless file_loaded?(__FILE__)
Test.initialize
file_loaded(__FILE__)
end
I made a few minor changes, mainly to increase diagnostic output, but then it works. One important change is that you shouldn’t name your module Test. An (undocumented) module of that name already exists in SketchUp, and your modifications may collide with it. I don’t think the name collision had any effect in this case, but changed it anyway as a precaution.
One needs to set up a model before running this. You need at least two pages (so there can be a scene change) , a material named [0020_Red], and something to select.
In change_color, I added a test for whether the selection is nil. Other than that, I’m not sure why your version isn’t working for you.
module ColorChangeTest
def self.initialize
Sketchup::Pages.add_frame_change_observer(SceneObserver.new)
end
def self.change_color
puts 'change_color start'
c = Sketchup.active_model.selection.first
puts "selection = #{c.inspect}"
unless c.nil?
c.material = '[0020_Red]'
puts c.material.inspect
else
puts "no selection"
end
puts 'change_color end'
end
class SceneObserver
def initialize
@curr_page = nil
end
def frameChange(from_page, to_page, percentage)
if percentage == 1.0 and to_page != @curr_page
puts "frameChange: page changed"
puts "curr = #{@curr_page.inspect}, from=#{from_page.inspect}, to=#{to_page.inspect}, pct=#{percentage}"
@curr_page = to_page
ColorChangeTest.change_color
puts "frameChange end"
end
end
end
end
unless file_loaded?(__FILE__)
ColorChangeTest.initialize
file_loaded(__FILE__)
end
Disabled all of my extensions and created a new model with two scenes and the color ‘[0020_Red]’. Replaced my test.rb with your ColorChangeTest code and still the same problem. ColorChangeTest.change_color works from the console but not when called from frameChange.
I’m using Sketchup 2015 Pro (15.3.331) 64 bit. What version of Sketchup are you using?
This is the output in the Ruby Console when I make a scene change:
frameChange: page changed
curr = #<Sketchup::Page:0x00000008061050>, from=nil, to=#<Sketchup::Page:0x00000008060560>, pct=1.0
change_color start
selection = #<Sketchup::ComponentInstance:0x00000008060dd0>
nil
change_color end
frameChange end
Can you show me the output from your console when your scene changes?
I’m using SU 2016 Pro. I just stumbled on what may be a key factor: the technique works only if there is a non-zero scene transition delay! If I either uncheck the box or decrease the delay to 0, it ceases to work. But even with an 0.1 second delay it works. This undocumented gotcha seems to me like a bug.