Also, one other question. I noticed that you wrapped your view observer class inside a module. Is there a benefit to using a module as the outer structure instead of a class? The way our plugin is currently set up, I would be using the view observer inside a class as opposed to a module.
As an aside, I answered only the question you posed in the original thread opener.
A bit off topic, (and we could start a new thread on it,) but I would not do it this way myself.
What I'd do using a scene page instead ... (click to view)
I’d set up a template with a scene page that had all the proper styling and layers off that needed to be invisible, and those on that need to be seen, with the camera in the proper position, and then I’d have a command that switched over to that scene page and use a FrameChangeObserver
to detect when the scene change was complete, then write the view to file. In this scenario again the observer would only be attached temporarily until the file was written. At the end of the command the page is switched back to the “work” page.
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.
https://github.com/Eneroth3/Eneroth-Face-Area-Counter/blob/master/src/ene_material_area/main.rb
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
A bit more complex model 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
Last edition looks neat
… 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:
This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.