Thursday, July 23, 2009

Dynamic Components that react to scene (aka page) changes

Have you ever wanted to have a Dynamic Component that reacts to Scene changes?



Well, this blogpost tells you how to write one (thanks to John Clemens for the idea).



First of all, you need a mechanism to detect a scene change. After some struggles with using the PagesObserver, I decided to use the FrameChangeObserver.



The add_frame_change_observer method is used to add a new FrameChangeObserver that is called with each frame of an animation. This means the end user has clicked on a Scene tab (aka Page) inside SketchUp and the camera is animating to that scene. Moreover, it provides you with a percent_done variable and fromPage and toPage variables which allow you to have finer feedback over the state of the animation.



The other challenge is that we need to find the DC that will react to the scene change. This can be accomplished by traversing the model entities and examining all the component instances that have a “dynamic_attributes” dictionary attached to them; those are the dynamic components in the model. Once we have a way to find all the dynamic components, it is just a matter of detecting which ones we want to influence on a scene change. For that, we add a SCENE_CHANGE Boolean (true or false values only) attribute to the top level attributes of our DC to indicate whether or not we want that DC to change. Then, we add a SCENE_TRIGGER attribute that contains an index that is affected by each scene change and that changes between 1 and 4 (this can be customized to be anything you want). This can then can be used to drive changes in the DC.



To see this in action, put the attached script in the Tools or Plugins folder of SketchUp, open the attached model, change the scene, and ta-dah the magic begins. You will notice that one of the two components does not change, the reason being that the attribute SCENE_CHANGE for that component is set to 'false'. Change it to 'true' and you will see it changing.



It is important to notice that there are several observers available from Ruby (e.g ToolsObserver, SelectionObserver, etc.) and any of them can be used to trigger changes in dynamic components using the same strategy I used in the attached script. DC can be made to change according to which tool is currently active, or depending on the currently selected material.


Download the example model here.



Code snippet below

----------------------------------------------------------------

# Copyright 2005-2008, Google, Inc.

# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#

# This code snippets allows you to have Dynamic Components
# that modify themselves based on a Scene Change
#
# They need to have at the top level the following attributes
# "scene_change" a boolean value that if true force the DC to react to scene changes
# and if false has the DC ignore the scene change
# and
# "scene_trigger" which is the index that is incremented every time there is a scene change and
# it is used to change attributes for the DC.
#
#
# Author: Simone Nicolo

require 'sketchup.rb'


# Define the Frame Change Observer which is documented in the Pages class.
class FrameChangeObserver

def initialize
# @current page is used to make sure that the observer is only triggered by page changes
# This is to workaround the PC bug that does not correctly populate the fromPage variable in the
# callback to frameChange.
@current_page = Sketchup.active_model.pages.selected_page
end

# Callback triggered by the Frame change observer.
# fromPage is populated with the page we were coming from, toPage is
# populated with the page we were transitioning to
# and percent done contains the percentage of the transition that
# has completed.
def frameChange(fromPage, toPage, percent_done)
# If there has been a page change and it has completed
if (not toPage.nil? and percent_done == 1) then
if @current_page != toPage then
#update the current page
@current_page = toPage
#find the DCs that need redrawing, and redraw them.
find_dc_for_redraw
end
end
end

# This function finds all the DC's that need to be redrawn based on a scene transition and
# on the boolean scene_change attribute if scene_change is true, change and redraw
# the DC on a scene transition, if it is false do not change or redraw the DC.
def find_dc_for_redraw
entities_list = Sketchup.active_model.entities
if entities_list != []
# For all the entities in the model
entities_list.each do |item|
type = item.typename
case type
# Find the Dynamic Component instances
when "ComponentInstance"
if not item.attribute_dictionaries.nil?
attributes = item.attribute_dictionaries[DICTIONARY_NAME]
# If they have a scene_change attribute that contains true
if (not attributes.nil? and attributes[SCENE_CHANGE] == 1.to_s)
# Increment the scene_trigger attribute.
# Here we are allowing the scene trigger attributes to only have the values
# 1, 2, 3, and 4.
attributes[SCENE_TRIGGER] = (((attributes[SCENE_TRIGGER].to_i+1)%5)).to_s
if attributes[SCENE_TRIGGER] == "0" then
attributes[SCENE_TRIGGER] = 1.to_s
end
# Redraw the DC using the $dc_observers global variable.
$dc_observers.get_latest_class.redraw_with_undo(item)
end
end
end
end
else
UI.messagebox("There are no entities in this model!")
end
end

end

if not file_loaded? 'scenes.rb'
# Useful constants
# Dynamic Components dictionary name
DICTIONARY_NAME = "dynamic_attributes"
# Boolean attribute that turns on and off the reaction to a scene change for the DC
SCENE_CHANGE = "scene_change"
# Index attribute for the DC that is used to drive changes in the DC.
SCENE_TRIGGER = "scene_trigger"
# Attach the frame chage observer to the global Pages object.
id = Sketchup::Pages.add_frame_change_observer(FrameChangeObserver.new)
file_loaded 'scenes.rb'
end


1 comment:

Todd Burch said...

Code formatted and properly indented here: http://www.smustard.com/forum/viewtopic.php?f=9&t=25