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 ?
Write code inside Animation.nextFrame to move objects without pages/scenes
Write code to add pages for each scene and loop through selected_pages in a loop
Write code to add pages for each scene and add frameChangeObserver and let user kick off animation through the sketchup interface.
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.
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.
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!
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.
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ā.
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.
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.
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?
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:
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.)
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Ėā¦
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.
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:
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 !