Waiting for Sketchup.send_action to complete before continuing

RE: Modules vs. Classes ... (click to expand) ...

First of all you MUST use a toplevel author or company namespace module.

Since it’s likely that you’ll have multiple plugins, each plugin should have it’s own module namespace wrapper to separate it from other of your plugins.

Since a plugin is a singleton object (there is only ONE instance of it,) it should be a module, which is an instance of class Module. A class is used to instantiate multiple instances of an object. A plugin scenario is not proper use of a class.

FYI, class Class is the direct child subclass of class Module, so they inherit everything modules have anyway, but more important only has different the instantiating functionality added, which is not needed by a singleton plugin / extension object.

The only reason that you see other people’s code of forum examples use class objects as a singleton plugin object, is because they wanted to call other methods in the namespace without namespace qualification. But this is a misuse of classes, and is not necessary.

There are two ways to use a module where methods can call each other without the self.method_name or MyModule.method_name qualification.

The older way:

module Author::SomePlugin

  class << self
    # define all methods here
  end

end

The now prefered way actually uses the module itself as a mixin module to extend itself (see Object#extend) …

module Author::SomePlugin

  extend self
  
  # define all methods here


end

I used the new way in the example.

With the old way it would be confusing as to whether to define constants variables etc., within the block of the anonymous singleton class instance or in the outer plugin module. With the new way everything is defined in that one module level.

... and continuing this _line of thought_ ... (click to expand) ...

If you look at @eneroth3's example on GitHub, you’ll see another way that also works. But this way does not allow the module to be used as a library “mixin” module. (But sometimes this is what you wish.)

This scenario defines all methods in the module as singleton methods. Then when you call them, the self. is implicit when accessing any objects within an object’s own namespace.

eneroth-face-area-counter/main.rb at master · Eneroth3/eneroth-face-area-counter · GitHub

Oh, okay, I see. We do have a module, but that module uses a lot of classes to accomplish the different parts of our plugin’s functionality, so I typically work in the class files rather than the module files, which is what led to my confusion.

According to my observations “view.zoom_extents” and “view.camera=” seem to work synchronously (at least on win, not sure about mac).

I’ve tested a rather big model (about 100000 edges and 40000 faces) with shadows and fog turned on. UI freezes while switching to a new camera and zooming, then storing takes place right after completion of switching to a new view and zooming. So seems like it is actually possible to get away from observers under windows.


I wasn’t able yet to code a test sample to demonstrate synchronous flow explicitly (and maybe it doesn’t work that way on mac after all). I hope maybe some engineers from SketchUp team may clarify this topic and maybe it would be great to add some more information to View and Camera chapters of Ruby API docs (in regards to synch/asynch flow of mentioned above methods).

Small question about a code sample.

Is there a reason why “remember_view” and “restore_view” methods in provided example could not be slightly reduced by storing a camera object itself instead of three values in a “remember_view” method (i.e. “@camera=view.camera”) and by using “view.camera=@camera” in a “restore_veiw” method?

Because I remember being involved with another topic in which we discussed that Sketchup::Camera#clone is simply inherited from Object and does not create a new Camera object (that is an exact copy of the original).
(ADD: [topic was] Camera Object Cloning Issue - Same as new with no arguments )

BUT, I forgot that the API implemented a ::new constructor for the class.

Anyhow, I was concerned that the reference to the view’s camera object that is returned by view.camera will not be a NEW object, so I thought it safer to store the 3 “descriptors” for the camera.set method. I have other code that I had to do this, so I was “running on past work”.

However, testing at the console, I see that view.camera returns a new object (on the Ruby-side) each time it is called, even when nothing has changed.

Testing at the console of v16
@cam = view.camera
change the view to “Top”
view.camera= @cam
nothing happens !

This is what I thot would happen, that the reference was pointing at the C-side view object what ever it is, and when it’s properties change, the reference is not magically made to point at a new object (on the Ruby-side) with the old properties.

So I forgot that there was a camera constructor, but in order to leverage this we’d still need to extract the 3 “descriptor” properties, viz:

  def remember_view()
    view = Sketchup.active_model.active_view
    cam = view.camera
    @camera = Sketchup::Camera::new( cam.eye,cam.target,cam.up )
    view.add_observer(@spy)
  end

NOW, we can just set the camera back to the previous camera without the array and the splat operator rigmarole:

  def restore_view()
    view = Sketchup.active_model.active_view
    view.remove_observer(@spy)
    restore_layers()
    view.camera= @camera
  end

My original actually takes a bit less code. If you find it easier, well okay use what you will. (Ruby is multi-paradigm.)

I just remember that @cam = view.camera would not work and that the 3 properties needed to be saved, and I find it easier to use the array.

Now if they’d implement a camera.clone method I’d promote that instead. I even wrote a test for that. I suppose I’d should attach it to a feature request and log it.


Now he did not even ask for that feature, but since others were encouraging him to change the camera, I thot I’d just include it as best practice. (Users might be upset if their previous view was not restored.)

You are free to prove me wrong here in any aspect and post an example of your own.

How about with textures on ? Xray ? There are many different combinations of the rendering options.

And using a zillion instances of the same exact component is not a “real world” scenario.
Use a very heavy model with many different materials and textures.

Compare against a light model with few or no textured materials.

If you notice I did not actually include “best practice” model operation with the disable_ui flag set.

Yes I would expect that SketchUp could not start the zoom until it had finished it’s previous view manipulation.

storing” is what your calling the PNG file write activity ?

Well I have plugins that I “shelved” because I was never comfortable with the implementation surrounding this asynchronous file saving behavior.

Perhaps some of these API method calls have been changed ? I doubt it. But then the API is now using Ruby 2.x so things may have changed greatly, also since the observers were overhauled.

I am saying that I have experience in that I’ve ALWAYS had to use some means to wait until SketchUp was done changing the view BEFORE attempt to export the view or write thumbnail. I’ve used observers, and timer blocks, and loops, like I showed above in the write_view_file() method.

When I tried this (in the past) I got an image that was not complete. (ie somewhere in the transition during the change.) So, I’m coming from past issues encountered. Maybe they no longer apply?

And the OP and I are on Windows not Mac.

I need to stress that this is not the problem I had in the past. I would expect these methods to happen one after the other as they are both API view manipulation calls.

It was file saving operations that would not wait for the view to finish rendering, in the past.

IF it’s since been fixed, and it now works, then great! (Trying to search through the API Release Notes is a nightmare.)

… and that SketchUp would not wait until file operations were done before resetting the view (or switching back to the previous scene page.)

It makes perfect sense, thanks for clarification.

Just for fun I’ve tried saving view of a simple model to a PNG on each view change, then applying saved PNG as a material texture to achieve the Droste effect:

Not sure if it proves synchronous PNG file write activity though :slight_smile:

A bit more complex model demo:
droste_effect_demo

Which means calling the file write inside the ViewObserver.onViewChanged callback, which is what I did in my example. (The whole point of making the image file write occur when the view is ready to be “read” for the “write”.)

BUT,… we have had issues in the past, where SketchUp Ruby will not wait until the file operations are done, … so I add in a loop that checks if the file exits.
However Windows has a sneeky habit of queueing up housekeeping task like updating it’s directory file tables in memory, so I use a trick of calling Dir.glob to force the system to refresh it’s “view” of the directory. I’m very often needing to manually press the F5 (refresh) key to reload directories for Explorer windows.
(May not be an issue of Mac OSX.)


I don’t really know if using the observer would prove synchronous behavior without using an observer.

It is interesting though. You write to the same filename in each callback loop ?

Yes to the same file name. Here is a code:

class MyViewObserver < Sketchup::ViewObserver
    def onViewChanged(view)
		model = Sketchup.active_model
		filepath="c:/temp/untitled.png"
		keys = { :filename => filepath, :antialias => true, :transparent => true }
		Sketchup.active_model.active_view.write_image(keys)
		image_rep = Sketchup::ImageRep.new
		image_rep.load_file(filepath)
		mat=model.materials["TestMat"]
		mat.texture=image_rep
    end
  end

model = Sketchup.active_model
view = model.active_view
observer=MyViewObserver::new
view.add_observer(observer)

As you admitted earlier API view manipulation calls from Ruby side take place one after the other (i.e. synchronously) so actually there is no need to use view observer to ensure that current view is “up to date” before PNG write activity I suppose. So in the sample above observer is used just to trigger PNG write activity after orbiting/panning/zooming performed by a user (not to ensure view refreshing after view manipulations from a Ruby side). The idea was to check current situation (in SU2018) in regards to file saving operations.

I think you mean that once file saving operation is called by Sketchup.active_model.active_view.write_image it takes place as some parallel process, and active model view may change during that process (still not sure how it caused saving of an incompletely refreshed view in the past).
Anyway feels like code inside of an onViewChanged in a sample above runs synchronously including Sketchup.active_model.active_view.write_image(keys) call in SU2018 under win8. It means texture refreshing in a model takes place when saving of PNG is completed.

Speaking of saving/restoring of a camera, I think it would be nice to save/restore two more parameters of a camera object (perspective and fov).

It happens when animating from one scene page to another. SketchUp did not wait until the transition was done. The image was a view in the middle of the transition. And then SketchUp also would not wait to reset the view.

This is a bit different, as there are no scene pages involved.

I’ve actually modified my sample and removed the ViewObserver and got a working plugin.

The strangest thing is that the user never sees any change in the viewpoint. It is like the write_image method is doing everything transparently in memory. If I didn’t check the directory for the written file, I’d think that nothing happened.

Did that already in the new edition. Because I changed the Top view to be an orthographic camera.

Looks like this now:

  def remember_view()
    # Reference the camera object for the active view:
    cam = @model.active_view.camera
    # Create a clone using the active camera properties:
    @camera = Sketchup::Camera::new(
      cam.eye, cam.target, cam.up, cam.perspective?, cam.fov
    )
  end

  def restore_view()
    # Set the model active view to use the cloned @camera:
    @model.active_view.camera= @camera
  end
1 Like

Last edition looks neat :+1:

… I wasn’t yet convinced …

but I modified my example removed the observer, switched everything to camera manipulation and it works! I am now convinced, and stand conrrected.


For that example Spencer (@scm04), see this separate thread:

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.