The methods of this observer fire in such a way that making changes to the model while inside of them is dangerous. If you experience sudden crashes, it could be because of this observer. A potential workaround is to use a ToolsObserver to watch what the user is doing instead.
My observers’ goals are:
Identify when a wall (component instance) is moved or deleted → so other walls can adjust their sizes, and windows can follow the wall (window components aren’t inside walls)
Identify when a window (component instance) is moved or deleted → to move or remove the opening
Identify when a window’s size changes (component definition) → to change the wall opening
Crashes happen suddenly but particularly when the user double-clicks on a group or a component (even if the component doesn’t have any link to any wall or window where observers are attached).
You trigger a movement that triggers another movement. This creates an avalanche of movements, which likely causes the buffer to overflow. Try disabling your observer when you trigger a movement, perform the required movement, and then re-enable your observer.
thanks for your answer but that was not case. its was the Sketchup::EntitiesObserver, that create this crash. I find a way to avoid this EntitiesObserver and its works.
I have used observers that edit the model with the following approach:
Setup a BuildQueue handler class. When an observer sees that an element of the model is edited, instead of triggering model edits, it adds an instruction to edit the model to the BuildQueue. It should check that the instruction isn’t in the queue already.
All of the edits in the BuildQueue should be processed as one transaction commit. Before you do anything, you need to have a way to turn off all of your own observers while you make your changes, otherwise you’ll get stuck in an infinite loop. The commit operation I tend to attach to the previous commit (so the start_operation method would use the flag “transparent: flag to true.)
Here is a class I made for my purposes - happy to share.
=begin
Name : BuildQueueClass
Author : Tom Kaneko (c) 2025
Version : 1.0
###
=end
module TK
###
# BuildQueue class handles the editing of the model. This class aggregates many requests to make edits to the model into one call, which happens when the BuildQueue::process_queue() method is called.
class BuildQueue
# The build queue is stored as a Hash of BuildEvent objects.
# The key is the template object to build, and the value is the BuildEvent
@@queue = {}
###
# Sets up the call for editing stuff
def self.add_queue(event)
@@queue[event.name] = event
puts "DEBUG: BuildQueue.add_queue " + event.name.to_s
end
###
# Processes the events queue, and does stuff.
def self.process_queue()
successes = {}
new_stuffs = []
@@queue.each{ |name, build_event|
new_stuff = build_event.execute()
if (!new_stuff.nil?)
new_stuffs << new_stuff
end
successes[name] = new_stuff
}
# Delete the items off the queue
successes.each{ |name, new_stuff|
@@queue.delete(name)
}
return new_stuffs
end
# Method deletes an action from the queue, using name as the key.
def self.remove_stuff(name)
if (@@queue.has_key?(name))
@@queue.delete(name)
end
end
def self.length
return @@queue.length
end
class << self
def queue
@@queue
end
end
end
###
# The BuildEvent class holds all the variables needed to build Stuff
class BuildEvent
###
# Accessors
attr_accessor :action_type
attr_accessor :waiting
###
# Constructor for BuildEvent
# @param stuff
# @param action_type (optional) - the kind of build event that occurs. Options:
# - :build - simply builds stuff
# - :update - removes previous stuff and builds new stuff
def initialize(stuff, action_type = :build)
# initialise the event with stuff
@stuff = stuff
@action_type = action_type
# etc
end
###
# Executes the buildEvent. Note that this operation may be carried out in the context of an observer, which can cause many issues. Therefore, it is important that all entities that we work with is checked for validity before doing anything with it.
# @return true on success, false on failure.
def execute
puts "DEBUG: Executing BuildQueue"
# Observers need to be turned off to avoid infinite loop
temp_deactivate_observers = TK.deactivate_observers
TK.deactivate_observers = true
# DO STUFF HERE, including commit
# Turn observers back on
TK::SkinUp.deactivate_observers = temp_deactivate_observers
end
end # class BuildEvent
###
# Model observer is used to process the BuildQueue after each commit operation.
class CommitModelObserver < Sketchup::ModelObserver
# class variable prevents commits from this observer comitting in an infinite loop.
@@deactivate_commit = false
def onTransactionCommit(model)
return if @@deactivate_commit || !CommitObserver.auto_update
puts "DEBUG: onTransactionCommit start"
# The following snippet from SaferObserverEvents: https://github.com/SketchUp/sketchup-safe-observer-events/blob/master/src/safer_observer_events.rb
executed = false
timer_id = UI.start_timer(0, false) {
break if executed
executed = true
next if BuildQueue.length == 0
# Ensure that the operation is transparent so the Undo stack isn't
# cluttered.
if (!TK.deactivate_observers)
TK.deactivate_observers = true
@@deactivate_commit = true
status = model.start_operation('Do Stuff (doesn\'t show anyway)', true, false, true)
raise LogicError, "Start Operation Error" if status == false
new_stuffs = []
begin
new_stuffs = BuildQueue.process_queue()
if new_stuffs.length == 0
puts caller
end
rescue
# Note: Never abort a transparent operation, it will abort the operation
# it chains to. Instead, try to clean up and simply commit in order to
# make sure the operation is closed.
# https://assets.sketchup.com/files/ewh/Observers2016.pdf
# TODO: the commit operation triggers a crash in the observers in DCobservers. Stack trace following commit:
# /users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `instances'
#/users/tomkaneko/library/application support/sketchup 2019/sketchup/plugins/su_dynamiccomponents/ruby/dcobservers.rbe:829:in `onComponentAdded'
model.commit_operation
@@deactivate_commit = false
TK.deactivate_observers = false
puts "DEBUG: onTransactionCommit end"
raise LogicError, 'SkinUp Build Queue processing failed'
end
puts "DEBUG: events processed: " + new_stuffs.length.to_s
status = model.commit_operation
puts "DEBUG: transaction commited"
@@deactivate_commit = false
TK.deactivate_observers = false
end
}
puts "DEBUG: onTransactionCommit end"
end
end # class CommitModelObserver
end # module
Thank you so much for this detailed explanation — your solution is really brilliant!
I really like the idea of using a build queue and only applying the changes during onTransactionCommit.
I’ll take the time to test this approach as soon as possible.
Thanks again for sharing both the code and the method!