Pause and Resume an Animation

If I pause an animation by simply returning true from nextFrame but then later resuming the animation from where it was when it was paused. I seem to have to click the view to get the animation to in fact resume. When I click the view window, the blue select box around the model flashes and the animation picks up again. Why the necessity to click the view window? Code below.

RotateGear.rb (1.1 KB)

Your profile states you are on SketchUp 2022. Is this still true?
I ask be cause the 2023 release uses a different GUI framework that may have issues.

I cannot test you code, because you did not provide a test model with the definition and instances.
I suspect the weirdness is a result of your misuse of the selection object.


A quick look at your example code ā€¦

(1) Ruby uses 2 space indentation.

(2) All the @@vars in your class should be @vars (ie, instance variables, as that is how you are using them.) In addition their initialization should be moved into an initialize instance method which is called automatically by a classā€™ new constructor method after the object has been created. Any variables passed to new are passed on to initialize.

(3) You have the statement mod = Sketchup.active_model, but then repeatedly call Sketchup.active_model.
Likely, you should assign it to @model in initialize and use that instance variable throughout.

(4) Is a biggie. There is no need whatsoever to stuff objects into the selection set in order to iterate them. The Sketchup::ComponentDefinition#instances method returns an Array that has an #each iterator (among others defined, as well as all those mixed in from module Enumerable.)

(5) I would suggest you calculate what you can before the animation loop starts. Probably this means within the initialize method. It is very inefficient to be getting the gear definitionā€™s instances collection anew and reiterating it and recreating each instanceā€™s rotational transformation again and again in every frame.
Ie, gears are pressed onto shafts and normally do not move. Their rotational transform will not change, ā€¦ they will rotate by the same amount, about the same axis point, each frame.

(6) In the rp = Geom::Point3d.new(ent.bounds.center) statement, it is frivolous to construct a point obejct. BoundingBox#center already returns a Geom::Point3d object.

(7) A toggle command usually reverses a Boolean variable:

def toggle_something
  @something = !@something
end

Meaning, in Ruby it is kind of weird to use 0 and 1 for Boolean state.


I know this is a little test code, but we encourage proper coding here for a shared environment. (It seems like the various AI bots search the web for examples and think bad code are gospel because it posted on the web and parrot it as answers.)

(a) All code for SketchUp should be within a unique namespace module. It also is best to separate each of your extensions from others within the namespace using submodules.

(b) Local variables should not be created at the top level within the ObjectSpace.

(c) Only Ruby classes and select API classes should ever be defined in the top level ObjectSpace (because they become global.)

(d) Code that creates UI objects (commands, menu items, toolbars, buttons) should be wrapped within a load guard so that they are only created once.

See next post for an example ā€¦

GearSpinner.rb (2.9 KB)

(Untested, except to be sure it loaded without syntax errors.)

For those that want to just look without downloading ā€¦

( CLICK HERE TO EXPAND )
# encoding: UTF-8

module Sguthery
  module GearSpinner

    class RotateGear

      VERSION ||= '0.2.3'

      @@animation = nil unless defined?(@@animation)

      ### CLASS METHODS
      #

      def self.instance
        @@animation
      end

      def self.pause
        @@animation.interrupt if @@animation
      end

      def self.resume
        @@animation.interrupt(false) if @@animation
      end

      def self.start
        model = Sketchup.active_model
        model.active_view.animation = nil if @@animation
        @@animation = self.new(model) # initialize called with model
        model.active_view.animation = @@animation
      end

      def self.stop
        if @@animation
          id = UI.start_timer(1.0, false) {
            UI.stop_timer(id)
            @@animation = nil
          }
          @@animation.cease
        end
        false
      end

      ### PRIVATE INSTANCE METHODS
      #
      private

      def initialize(model)
        @model = Sketchup.active_model
        @rd = 0
        @rv = Geom::Vector3d.new(0,0,1)
        @pause = false
        instances = @model.definitions['Gear1'].instances
        # Create a Hash with instance keys and their transform values using the
        # Hash::[] class constructor method that takes: [ [key1, value1], ... ]
        @transforms = Hash[
          # Map instances array to a new array of [instance, transform] subarrays:
          instances.map do |inst|
            [ inst, Geom::Transformation.rotation(inst.bounds.center, @rv, 0.0175) ]
          end
        ]
      end

      ### PUBLIC INSTANCE METHODS
      #
      public

      def cease
        @model.active_view.animation = nil
        puts("Stopped #{@pause} at #{@rd}")
        false
      end

      def interrupt(parameter = true)
        if parameter
          @pause = true
          puts("Paused #{@pause} at #{@rd}")
        else
          @pause = false
          puts("Resumed #{@pause} at #{@rd}")
        end
      end

      def nextFrame(view)
        if @pause
          sleep 1.0
          return true
        end
        puts("frame")

        @transforms.each do |inst, trans|
          inst.transform!(trans)
        end
        view.show_frame

        sleep 0.2
        if @rd < 100
          @rd = @rd + 0.5
          return true
        else
          @rd = 0
          self.class.stop # returns false
        end
      end # nextFrame()

    end # class

    if !defined?(@loaded)

      submenu = UI.menu("Plugins").add_submenu("Gear")

      submenu.add_item("Start") { RotateGear.start }

      submenu.add_item("Pause") { RotateGear.pause }

      submenu.add_item("Resume") { RotateGear.resume }

      submenu.add_item("Stop") { RotateGear.stop }

      @loaded = true
    end

  end # submodule
end # module Sguthery
1 Like

Thanks much for your comments, Dan. Greatly appreciated. Yes, Iā€™m on 2022, which is what Trimble sold me about a week ago (just before the price increase!).

I swizzled your GearSpinner code a little differently ā€¦
gears = RotateGear.new
@@animation = gears
submenu.add_item(ā€œStartā€) { Sketchup.active_model.active_view.animation = gears }
but the gear goes round and round just fine.

You used a couple of methods on @@animation that I donā€™t find in the API docs at Module: Sketchup ā€” SketchUp Ruby API Documentation, namely, cease and interrupt. Maybe these are in 2023 or perhaps there are updated docs somewhere?

Does a yearā€™s worth of Pro entitle one to a free update from 2022 to 2023?

Cheers, Scott

If you look again at the documentation for Animation, in the class Overview section, take notice of the box note with emboldened: ā€œThis class is abstract.ā€ It continues and informs you that ā€œā€¦You can not sub-class this class because it is not defined by the API.ā€

So, it means that there is no actual Sketchup::Animation superclass. It is only shown within the Sketchup module so the YARD documentation generator will document it, and because if it was shown as a global class, coders might think it was a core Ruby class.

Abstract classes do not have a superclass other than Object because they do not need to inherit special functionality, other than what basic functionality all classes in Ruby inherit. For the API, abstract classes need to define publicly accessible methods (some of which are optional) the SketchUp engine will poll for and call if defined.

From now on, take notice of API classes that are marked as abstract. All the SketchUp API observer classes are abstract since v2016.


Okay, so since an abstract class is not an API class (that you should never modify,) you are free to define extra methods within so as to make a well written and working class.

In the example, I had to choose instance method names other than pause and stop because those are the names of the optional callback methods the API would poll for. (They really should have been named like the APIā€™s other callback methods ā€¦ ie, onPause and onStop, but this was decided like 20 years ago.) So, this is why I named them interrupt and cease.

1 Like
Off-Topic Re: Version and licensing ... ( click to expand ) ...

I havenā€™t heard of a price increase recently. EDIT: Okay I see discuss that itā€™s going up in July.

Perhaps, they would prefer new users stay on 2022 until the next update to 2023.1 because of some current issues with 2023.0.2. One of them is that model files are going corrupt. Another is issues with the inspector dialogs being black out or not updating.

Itā€™s a subscription license, not a classic license for a single version. You can install 2023 at the same time, They install into separate program directories. You use the same subscription license.

However, the last one installed will be the default that double-clicking a SKP file will open into. If after installing 2023, you still want to open SKP files in 2022 when double-clicked, then you will need to rerun the 2022 installer and choose the Repair option.

Why would you do this? It is all handled in the RotateGear::start class method.
All the menu item block need do is call this class method.

Dan:

Thanks again for your comments. Theyā€™re all informative and very useful.

But even with the code you kindly provided, I have to click the window or tap a key to get the animation running again after I pause it. In fact, just returning ā€˜trueā€™ to nextFrame and doing nothing else stops the nextFrame loop altogether. A click or key tap will trigger one more call to nextFrame and then crickets.

However, if I call view.show_frame before just returning true (even though there has been no change to the model) the regular calls to nextFrame continue. Having to refresh a display that hasnā€™t changed to get functionality that doesnā€™t depend on the display is a little counter-intuitive, but it does work around the problem.

Thanks again for your guidance.

Cheers, Scott

P.S. There might be a single-step debugging utility of this SketchUp property but running an animation to single-step a script is probably ill-advised.

Okay. It might be an actual issue that should be reported in the official tracker at GitHub.

But again, nobody else can reproduce without a test model that had the gears and the component definition named ā€œGear1ā€.