Recommendations for animation code?


#1

A little help needed on getting started on animation and scenes using Ruby. Yes, I am comfortable
with developing plugins for Sketchup. My question is as follows. I see two sets of APIs for animation. One is the Animation object and using the nextFrame method (su_examples has a animation.rb which manipulates camera). My requirement is moving multiple objects and I see adding scenes/pages and adding frameChangeObservers to pages as the second method. Also, in order to kickoff
an animation thru script I need to do active_model.pages.selected_page= xxx .

From the wisdom of the community, what is recommended ?

  1. Write code inside Animation.nextFrame to move objects without pages/scenes
  2. Write code to add pages for each scene and loop through selected_pages in a loop
  3. Write code to add pages for each scene and add frameChangeObserver and let user kick off animation through the sketchup interface.
  4. A different code flow…

Yes, I am aware of a few commercial plugins like su animate and keyframe animation. But I may need to customise a lot and do not have the luxury of using off the shelf plugins.

Ravi


#2

I use Sketchup for animation as a hobby and have some comments:

One is the Animation object and using the nextFrame method . . .

I find creating a class to handle nextFrame just creates extra code that makes the program more difficult to understand. A benefit it offers is that when the nextFrame message handler is complete, it returns control to Sketchup and you can interact with the model between each frame.

I see adding scenes/pages and adding frameChangeObservers to pages as the second method

If you need to have real animation during Sketchup’s built-in scene-to-scene camera interpolation, then frame change observers allow that. One of the arguments to the “frameChange” method is a ratio that allows the developer to know how far the scene’s camera interpolation has progressed, allowing a potential “real time” animation.
I find observers unintuitive, and Sketchup doesn’t allow user’s to purge all observers. Although Sketchup can write video files of scene-to-scene animation, it does not run any Ruby code during the “File…Export…Animation” menu option.

A different code flow…

The “nextFrame” and “frameChange” methods are message handlers called by Sketchup, and not by any developer code. I find they create additional unnecessary code and are less intuitive. The vast majority of my animations involve the camera and grouped objects. If a developer uses the “move!” method that groups and component instances support, then the animation isn’t slowed down by logging of edit undo operations, and it isn’t necessary to include edit…undo exception handling. Since the “move!” method by itself doesn’t refresh the view, it is necessary after repositioning models to perform “view.refresh”. If it is necessary to animate loose drawing objects like faces and edges, or vertices, then the “transform_by_vectors” and “transform_entities” methods are required (which can also animate group and components), but they do log edit undo operations. A developer can replace the “view.refresh” with “view.write_image” to write each animation frame, and then merge them together outside of Sketchup. The image writes though take quite a bit of time. The move and refresh statements are in a simpler loop that outputs a frame each time through the loop.

These programs are Ruby scripts, but they are not plugins and I do not develop them in the plugins folder or subfolders.


#3

Bruce, any chance you can post or point to your animations? My interest can be found by searching the forum for Sketchup and SETI. Looking forward to knowing more!


#4

Thanks Bruce. I like your idea of using move! and delinking it from the sketchup callback methodology. One question I had on your statement

The move and refresh statements are in a simpler loop that outputs a frame each time through the loop.

The loops typically run thru very fast. How do you give the feel of slow animation. Or is that done outside of sketchup when you stitch them together.

Ravi


#5

For scene page transition animation speed and delay settings, see the Model Info > Animation panel.

Via Ruby, these are accessed via the Sketchup::Model#options() instance method, which returns a collection object of class OptionsManager, to access objects of class OptionsProvider.

anim_opts = Sketchup::active_model.options["SlideshowOptions"]
anim_opts.each {|key,val| puts "#{key} = #{val.to_s}"}

LoopSlideshow = true
SlideTime = 1.0

Notice the global transition time is not exposed. We have to set them individually upon each scene page object (or set the first one and hope new scene pick up the value.)


For your own Animation class loop. Ruby has a global sleep() method that might be used for short pauses. But don’t pause very long, or else Windows will set SketchUp’s application to “Not Responding”.


#6

Thanks Dan. I just noticed view.show_frame takes parameter which allows the frame to be redrawn after some time. That should cover it. I am toying with using a mix of Bruce’s suggestion of move! and animation class rather than scenes to reduce the complexity.


#7

Oh, you most definately need to use move! to keep the object movements out of the undo stack.


#8

The Animation class would be my choice; in fact somewhere I have some basic animation code which I used it to make this:

Another way would be to use a plain old UI.start_timer() as part of an animation engine.

Here are a few more fairly boring tests: https://www.youtube.com/playlist?list=PLO5DTUNiJtebP9Dtaor39mBvE5kskL1H1


#9

You can slow things down by moving a shorter distance each frame, or putting in a delay as @DanRathbun mentioned. A code snippet is below:

  entity = Sketchup.active_model.entities[0]
  view = Sketchup.active_model.active_view
  number_of_frames = 36
  angle_change = 360.degrees / number_of_frames.to_f
  rotform = Geom::Transformation.rotation( ORIGIN, Z_AXIS, angle_change )
  number_of_frames.times do
    entity.move! rotform * entity.transformation
    view.refresh
  end

The code assumes that the first element in the entities collection is a group or component (on my startup template the Sketchup startup person). This program will return the model near its original position (not exactly because of precision errors), but the error is not noticeable.

If it is too quick, then increase the number of frames to perform the same amount of movement. The program above is very fast on my computer. Try increasing variable “number_of_frames” to 360 or 1000.
Comments:

  • The amount of time it takes to refresh one frame is related to the number of total drawing elements in the file. A given Sketchup file’s frame rate will not only vary from computer to computer, but the loading on the existing processor from other programs.

  • As long as the Ruby code is running, the user cannot interact with the rest of Sketchup until the program terminates. When animating for more than a few seconds, Windows does treat the app as “Not Responding” until the animation ends, but the animation continues to run.


#10

although warned about bumping the topic…

due to the slowness of rotating large amounts of geometry, I’ve been trying to create a similar visual result by rotating the camera, targeted on the same entity.bounds.center

to avoid the jump from camera.target to bounds.center I added an incremental step towards the desired target…

this is some test code for looking at the progression in Ruby Console…

# basic ingediants
model = Sketchup.active_model
view  = model.active_view
cam   = model.active_view.camera
ents   = model.active_entities
sel    = model.selection
bbc = sel[0].bounds.center.to_a rescue ents[0].bounds.center.to_a

# step camera target to bounds center
p a = cam.target.to_a
b = bbc # @@pt1.to_a #
count = 10
target = []

# test for the progression
count.times  do
	c = a.zip(b)
	target = []
	c.each do |i|
	 target << ( i[0] + ((i[1] - i[0])/count ))
	end
	a = target
	count -= 1
	p target if count.modulo(1) == 0
end
target == bbc

in my plugin it happens over a 360 degree rotation and needs a second spin to show what I really want…

is there a better way?

are there some clever maths that avoid array manipulation?

john


#11

From what I understand, you just want to interpolate position (keeping camera direction the same). The target needs to be adjusted as well. I haven’t tested this, but Point3D linear combination could be used as follows:

. . .
initial_posn = cam.eye.clone
final_posn  = sel[0].bounds.center.to_a rescue ents[0].bounds.center
count = 10

count.times do |counter|
  ratio = counter.to_f / count.to_f
  new_eye = Geom::Point3d.linear_combination( (1-ratio), initial_posn, ratio, final_posn )
  new_target = new_eye.offset( cam.direction )
  new_camera = Sketchup::Camera.new( new_eye, new_target, cam.up )
  . . .

This assumes that you want to keep the same camera orientation. An alternative would be to apply a translate to camera eye and target positions.


#12

can’t upload gifs at the moment, unfortunately…

for each step [360 in full code] I change the camera.eye and camera.target

I did’t want to create 360 new cameras so I’m using cam.set(eye_point, target, up)

up is

the eye point is from an array of points defined by a planar circle at eye height centred perpendicular to the bounds centre…

when the target starts as bounds.center I get a noticeable jump as I start the rotate…

by stepping I avoid the bump, but it takes a full rotation…

after that, it behaves the same as using rotation handles on the move tool, which is the desired out come…

I’ll try a linear_combination applied to the current camera…

cheers

john


#13

Although you can use #offset or #offset! on Array or Geom::Point3d objects, many coders forget that they (and Geom::Vector3d objects,) can be transformed.

In this scenario it doesn’t matter whether #offset or #tranform is used as they both take a vector object.

Leaving the eye in the old place and sweeping the target between old target and bb center:

# given the basic ingredients:
bbc_vec = cam.target.vector_to(bbc)

if bbc_vec.length > 0
  steps = 10
  step_len = bbc_vec.length / steps
  vec_t = bbc_vec.clone
  vec_t.length = step_len

  delay = 0.25 # secs

  steps.times do |i|
    sleep(delay) if i > 0
    aim = cam.target.transform(vec_t)
    cam.set(
      cam.eye,
      aim,
      cam.eye.vector_to(aim).axes.y
    )
    view.refresh
  end

end

EDIT:

So in my example the cam.eye argument in the cam.set call can also be transformed by the same vec_t, and then the cam.up vector should remain the same (and not need computing from the other arguments.)


#14

cheers, that works for me if I use

    eye = cam.eye # leave it as is
    aim = cam.target.transform(vec_t)
    up  = cam.up # leave it as is
    cam.set(
      eye,
      aim,
      up
    )

otherwise it will flip when the model is inverted…

next issue is can rotation around bbc[0], bbc[1], eye[2] be applied to the directly to eye?

EDIT: I can finally add a gif I made for my first post…

john


#15

this is what I’m wanting to replace with a camera animation on larger models…

in my extension, the code is always run on a locked group so I’ve modified it for testing remotely…

oddly, sel.add(ents[0]) if sel.empty? only highlights the group on the first run…

def spinit
  model = Sketchup.active_model
  ents  = model.entities
  sel   = model.selection
  view  = model.active_view
  sel.add(ents[0]) if sel.empty?
  grp   = sel[0]
  bb    = grp.bounds
  vec   = Geom::Vector3d.new(0, 0, 1)
  bbc   = bb.center
  rot   = Geom::Transformation.rotation(bbc, vec, 4.degrees)
  model.start_operation('spinit')
  90.times do
    model.active_entities.transform_entities(rot, grp)
    view.refresh
  end 
  ensure  model.abort_operation
  return true
end # spinit  

spirit seems very effective on smaller models and I’m using Sketchup.active_model.number_faces > 5000 to either spinit or invoke my spin camera class…

  def spin
    # only load if needed
    # require_relative 'orbital_spin_class_arc'
    load File.join(__dir__, 'obital_spin_class_arc.rb'
    Sketchup.active_model.active_view.animation = JcB::OrbitalCamera.new
  end # spin

after add @DanRathbun’s move it looks like this…

although better than a ‘jump’ it needs more than the 90 steps of the spinit to look as smooth and it stops short of 360˚…

john


#16

I can now see what you wanted. You said:

Animating the camera avoids logging of edit…undo operations. Instead, you could select all the objects and group them into one entity. They could be rotated with a rotation transform
and the “move!” method “entity.move! rotation * entity.transformation”.
I wonder if you would notice any frame rate difference.


#17

on up to 30MB files both seem about the same…

on a 126MB test file, that takes 70 minutes to process before the spin, the camera move is step-y but much faster…

it always happen on a pre-positioned locked group…

the whole process up to the spin is in a tmp file, and the spin, blocks SU from serving the ‘Save As’ before the model is ready…

and it’s entertaining…

this is the basic concept…

john


#18

Confirmed also on PC v 2016.

The bug here is that the selection set is not empty, but the items are not highlighted.
Calling sel.add(sel.to_a) does nothing. It does not remove them as the docs says it should, nor does it change the selection because the items are already there. Calling view.refresh or view.invalidate also does nothing.

The only workaround is:

a = sel.to_a
sel.remove(a)
sel.add(a)

Which is totally weird because the docs says the #add(), #remove() and #toggle() are all aliases for each other. And this does not work:

a = sel.to_a
sel.add(a)
sel.add(a)

@tt_su ?


#19

Bruce, move! doesn’t seem to work if you only have a rotation, which is all I need to manipulate geometry…

  rot   = Geom::Transformation.rotation(bbc, vec, 4.degrees)

  90.times do
    # model.active_entities.transform_entities(rot, grp)
    grp.move! rot
    view.refresh
  end

If run in my example code does a single move, that doesn’t seem to have any relationship to the transformation…

john


#20

I see this also, (SketchUp 2016, 2015 & 2014) but it happens with any kind of transformation, even a vector translation.
It does not matter of it is a loop or just the nextFrame callback of an Animation object. The move!() method will only do a transform one (or two times at most.)

If I have puts calls in there to print the iterator step, and have started the animation from the Ruby console, I must click in the model window to advance the loop. Which is a bit weird.

The move!() method was designed to work within animations. What is going on !