I assume you mean on Windows…please describe in more detail.
PC==Windows.
The ‘However,…’ line follows on immediately after what I said about PCs.
MAC and PC vary because of the different way they load a SKP in one and multiple instances of SketchUp respectively.
However, on a PC during a session aspects of ‘ruby’ use can be carried across.
If you have a @/@@variable set up in one SKP… then in-code open another SKP that variable ought to carry over.
On a MAC since it’s the same SketchUp instance it should carry over too.
The main difference I notice between MAC and PC is that if I open a SKP from with SketchUp.
On a MAC the former SKP also stays open.
On a PC the original SKP closes and I still have one SKP open.
However, on a PC there are still several ways of opening a second SKP whilst leaving the current one open…
The observer engine, polls each attached observer in turn, using respond_to?(:callback_name) to determine IF that particular observer actaully has the named callback. IF so, it then calls the callback for that observer, … if not, it skips to the next observer in it’s attached observer stack.
I found this one hidden away,
not sure how much is my code, it’s probably a mashup…
seems to work for all mac scenarios, including startup if in plugins folder…
module ModelChangeObserverTestJcB
# This is an example of an observer that watches the application for
# active model changes and updates a webdialog.
class MyAppObserverJcB < Sketchup::AppObserver
@msg = 'no message'
def expectsStartupModelNotifications()
return true
end
def onNewModel(model)
@msg = "onNewModel:"
update_window(model)
end
def onActivateModel(model)
@msg = "onActivateModel:"
update_window(model)
end
def onOpenModel(model)
@msg = "onOpenModel:"
update_window(model)
end
private
def update_window(model)
puts @msg
ModelChangeObserverTestJcB.update(model)
end
end
def self.create_window
@window = UI::WebDialog.new
@window.set_position(200,200)
@window.set_size(440,200)
@window.set_html('<html><body>[No Data]</body></html>')
@window
end
def self.update(model)
@window ||= self.create_window
if @window.visible?
@window.bring_to_front
else
if Sketchup.platform == :platform_osx
@window.show_modal
else
@window.show
end
end
html = <<EOT
<html>
<body>
<h1>#{model.title}</h1>
<h2>#{model.to_s.gsub('<', '<').gsub('>', '>')}</h2>
<h2>#{model.path}</h2>
</body>
</html>
EOT
@window.set_html(html)
end
# Attach the observer
Sketchup.add_observer(MyAppObserverJcB.new)
self.update(Sketchup.active_model)
end # module
EDIT: Sorry, duplicate post
Testing on MAC, I think this ModelChangeObserverTestJcB still has the same issue that if the user closes the last model window without exiting SU, the dialog doesn’t get cleaned up.
I have a working class I’ll post here in case it helps others. Since I have several different dialogs to manage, I have a superclass that takes care of the dirty work. Thanks to all for your input.
The sub class will have to:
- Call the super’s init function with a window title: self.init(title=‘My Nifty Dialog’)
- Define its own html text string to display in the dialog
- Have a “callRuby(actionName)” or similar in the JS which employs the “window.location=‘skp:send:cmd@’ + actionName” protocol
- Call super(HTML_text, wid, len, x, y, polling) in its own initialize()
- Contain a refresh() method which updates the contents of the dialog when needed
- Contain a receive(action) method which receives actions from JS
- Determine how the dialog will get instantiated (i.e. create a UI menu or the like)
Super class:
class AttrDialog
class << self #Store instances array in the class object so each child class has its own version
attr_accessor(:instances, :title, :appobs)
def init(title)
@instances = Hash.new if not defined?(@instances)
@title = title #Title for dialog window and menu item
@appobs = nil #application observer
end
end
# Call this any time a model becomes the current active model
# -----------------------------------------------------------------------------
class AttrAppObserver < Sketchup::AppObserver
def initialize(c);
@myClass = c
end
def expectsStartupModelNotifications(); return true; end #For backward compatibility<SU2014
def onOpenModel(model)
@myClass.newModel(model)
end
def onActivateModel(model)
#printf("Activating:%s self:%s\n", model, self)
@myClass.newModel(model)
end
end #AttrAppObserver
# Call this any time the selection changes
# -----------------------------------------------------------------------------
class AttrSelObserver < Sketchup::SelectionObserver
def initialize(dialog)
@dialog = dialog
end
def onSelectionBulkChange(selection)
@dialog.refresh
end
def onSelectionCleared(selection)
@dialog.refresh
end
end #AttrSelObserver
# Called from menu to create/launch new dialog if necessary
# -----------------------------------------------------------------------------
def AttrDialog.check(mm = nil) #Class method so it can be called before any objects have been created
#printf("In check model:%s self:%s inst:%d\n", mm, self, self.instances.length)
return self.instances[mm] if mm #If a model specified, tell whether editing for this feature is enabled or not
mm = Sketchup.active_model #Otherwise, assume active model
if self.instances[mm] #If a dialog exists, delete it
self.instances[mm].close
else #Otherwise, create one
self.instances[mm] = new() if !self.instances[mm] #Create a dialog for this model if we don't already have one
self.instances[mm].refresh #Update the dialog widget
end
end #check
# Show only the dialog for the specified model (or hide all)
# -----------------------------------------------------------------------------
def AttrDialog.newModel(m = nil) #Class method so it can be called before any objects have been created
#printf("In newModel:%s self:%s\n", m, self)
self.instances.each_pair { |mod, dia|
return if !dia
if (mod == m); dia.refresh; else; dia.hide; end
}
end #newModel
# -----------------------------------------------------------------------------
def AttrDialog.closeAll () #Close all instances
self.instances.each_value { |i| i.close}
end #closeAll
# -----------------------------------------------------------------------------
def initialize(html, x, y, xo, yo, ping = 1000) #When object created
#printf("AttrDialog initialize\n")
@myTitle = self.class.title
@myDialog = UI::WebDialog.new(@myTitle, true, nil, x, y, xo, yo, true)
@myDialog.set_html(html) #Show our html content
@myDialog.add_action_callback("send_cmd") { |dialog, action| # Attach an action callback to the dialog window
receive(action) #Our handler for calls from ruby
}
@myModel = Sketchup.active_model
@myModel.selection.add_observer(@selObserver = AttrSelObserver.new(self))
Sketchup.add_observer(self.class.appobs = AttrAppObserver.new(self.class)) if !self.class.appobs
@myDialog.add_action_callback("ping") { |dialog, action| # Observers can't tell us when last model closed on MAC platform so we have to poll from javascript
#printf("Catch ping: %s %s\n", dialog, action)
close() if (not @myModel) || (not @myModel.valid?)
}
@myDialog.show { #Polling pinger helpful on MAC to close any stray windows
@myDialog.execute_script("pinger=setInterval(function(){ window.location='skp:ping@pong'; }, #{ping});") if ping > 0
refresh
}
end #initialize
# Turn off this dialog
# -----------------------------------------------------------------------------
def close () #Destroy object
if @selObserver && @myModel && @myModel.selection #Cancel our selection observer
@myModel.selection.remove_observer(@selObserver)
@selObserver = nil
end
@myDialog.execute_script("clearInterval(pinger);")
@myDialog.close #Close the dialog window
self.class.instances.delete(@myModel) #Forget about this instance
Sketchup.remove_observer(self.class.appobs) if (self.class.instances.length <= 0) #If this was the last instance
end #close
# Close the dialog window without destroying the dialog object
# -----------------------------------------------------------------------------
def hide ()
@myDialog.execute_script("window.close()")
end #hide
# Make sure our dialog window is visible
# -----------------------------------------------------------------------------
def show ()
@myDialog.show() unless @myDialog.visible?
end #show
end #AttrDialog
Sample sub class:
# A sample dialog window which uses the AttrDialog superclass
# -----------------------------------------------------------------------------
require_relative 'attrdialog.rb'
# Dialog for examining and modifying Sketchup entity parameters
# -----------------------------------------------------------------------------
class TestDialog < AttrDialog
self.init(title='A test Dialog')
# HTML code to display in a webdialog for editiing parameters for our object
# -----------------------------------------------------------------------------
HTML_text = %q^<html><head>
<script type="text/javascript">
function callRuby(actionName) {
query = 'skp:send_cmd@' + actionName;
window.location.href = query;
}
function updateMessage(msg) {
document.getElementById('message').innerHTML = msg;
}
</script></head>
<body>
A test Dialog Window
<p>
<div id="message">Message goes here</div>
<input type="button" value="A Button:" onclick="callRuby('myAction')" title="Press to send a message to Ruby">
</body>
</html>^
# -----------------------------------------------------------------------------
def initialize ()
super(HTML_text, 360, 280, 80, 500)
end #init
# -----------------------------------------------------------------------------
def refresh () #Refresh the info in the dialog
show
printf("Refreshing\n") #Update contents of dialog with latest info...
@myDialog.execute_script("updateMessage(#{Time.new().to_s.inspect})")
end #refresh
# -----------------------------------------------------------------------------
def receive (action) #Receive commands from webdialog
printf("receive action_name:%s \n", action)
@myDialog.execute_script("updateMessage('Got button')")
end #receive
end #TestDialog
TestDialog.check #Instantiate our window
Okay, I’m running Windows, so I can’t test for the real issue that OSX has.
But, rather than using a javascript interval, why not a UI timer? The following code (for the most part) leaves SU responsive. FWIW, I’ve used both timers in code. Both can be helpful for unwinding the call stack and removing code blocking…
Important - timer doesn’t stop, you have to shut down SketchUp
class ClosedModel
def initialize
@id_am = Sketchup.active_model.object_id
@id_tmr = nil
@t0 = Time.now.to_f
start()
end
def start()
@id_tmr = UI.start_timer(1.0, true) {
if @id_am != Sketchup.active_model.object_id
puts 'Model Closed %10.2f' % (Time.now.to_f - @t0)
# stop timer for other code?
@id_am = Sketchup.active_model.object_id
end
}
end
end
ClosedModel.new()