Difficulties wrapping the operations of a plugin into one undo

I’ve written a plugin which is based on the parametric class. My plugin creates two classes depending on the parametric class. parametric’s create and edit methods have start_operation/commit_operation functionality built in. I have additonally added start_operation/commit_operation functionality wherever my plugin alters the model but still I am left with over 50 undos when I use the plugin for the simplest objects. Where might be the problem. Below is how I call my plugin

# Prompt for parameters and then insert windows
def WinDoor.create
	model = Sketchup.active_model
	model.start_operation("Create WinDoor", true)
	windoor_instance = WinDoor.new
	# puts "in WinDoor.create and windoor is #{windoor_instance}"
	definition = windoor_instance.entity
	# puts "in WinDoor.create and definition is #{definition}"
	Sketchup.active_model.place_component definition, true
	model.commit_operation
end # WinDoor.create

Any help would be really appreciated.
Thanks,
Francis

First of all, please put your code inside a code tag. Like this:

# Prompt for parameters and then insert windows
def WinDoor.create
  model = Sketchup.active_model
  model.start_operation("Create WinDoor", true)
  windoor_instance = WinDoor.new
  # puts "in WinDoor.create and windoor is #{windoor_instance}"
  definition = windoor_instance.entity
  # puts "in WinDoor.create and definition is #{definition}"
  Sketchup.active_model.place_component definition, true
  model.commit_operation
end # WinDoor.create

The start_operation has 3 more parameters (documentation):

If you have a start_operation and, before doing a commit, you call a method that have another start and commit operation, then that second commit will commit the first operation too.

Maybe the initialize of your WinDoor.new have some commit operation.

To resolve that (if it’s the case) you could just apply the 3rd and 4st boolean paremeters which “merge” the operations that occurs after and before (respectively) into one.

Francis,

As mentioned, you can combine all your operations. Also, you can create a counter for the number of operations added to the stack, then do

1.upto(cntr_ops) { Sketchup.undo }

to remove them.

Greg

Greg, Kimpastro,
thank you for your advice which I will try out soon. I’ll get back soon with a report on whether I succeeded or not.
Til then,
Francis

Greg, Kimpastro,
I’m still having trouble with model.start_operation/commit_operation. I think it may be due to unintentional nesting. I have code which does things like:-

class Topclass < Subclass
 def Topclass.method1
    model.start_operation("method1",true,false, true)
    model.commit_operation
 end
 def Topclass.method2   
    model.start_operation("method2",true,false, true)
   code..
   Topclass.method1
   code
   model.commit_operation
 end
 Topclass.method1
end #of class Topclass
class Subclass
end # class

Subclass

method2 calls method1 and method1 gets called on its own. Both methods have undo functionality. Must I get rid of all called methods undo functionality (Topclass.method1) if the calling method has already got undo functionality(Topclass.method2). What if I call Topclass.method1 outside of Topclass.method2? Then I have a lot of undos caused by Topclass.method1. I’m wondering what is the best fix?

Francis

The 1st issue is you are attempting to use classes when you should be using modules.

… and please wrap your code samples within

```ruby

and

lines, using 2 space indentation.

Francis,

When the 3rd and 4th arguments were added to the start_operation method, I tested them and found they didn’t help with my problem. Since then, I’ve only used two parameters with start_operation.

I assume that you’re aware that the edit Undo stack can only delete the last operation. Hence, ‘Both methods have undo functionality’ may not be attainable. Also, it’s not clear that by ‘undo’ you mean ‘Model.abort_operation’ or ‘Sketchup.undo’.

I might suggest setting up your app such that the first object that issues a start_operation sets a flag that the other objects can read. Given the ‘nesting’ you’ve described, it should work such that only the top level object starts and commits the operation.

Greg

1 Like

Dan,
I am extending the existing Parametric class. In the actual code I do wrap the whole thing in a module. My issue is with the start_operation/commit_operation methods for Sketchup.active_model. No matter what I do I am left with more than one undo when I create a new object. Like I wrote in an earlier post, I think it is maybe some unintentional nesting of the start_operation/commit_operation methods. If I call a method with these methods already in it from a method which also has the methods in it that surely causes nesting?

Sorry my code example was not formatted to standard.

Greg,
I’m sorry but I seem to be confusing the english word undo with the Sketchup Ruby stack undo method. With undo I have meant the Sketchup.active _model.start_operation/commit_operation functionality, which should step you back in a single step to where you were before the plugin/extension started. Like I wrote to Dan, I think I have an unintentional nesting problem due to the fact that the Parametric class (the Subclass in my pseudo code) uses a method called edit, which it employs even when creating my derived class. edit has Sketchup.active _model.start_operation/commit_operation functionality and so of course, does my super class in it’s create method. I have added - true, false, true- as arguments to the edit method’s start_operation but that hasn’t gotten rid of the problem of superfluous undos after running the extension. That is, I guess, because edits start_operation/commit_operation is nested in create’s.
Having said all that, I very much like your idea of setting a flag. That sounds like it could work Thank you very much for the idea which I will try out soon.

As I mentioned, I have had issues with the 3rd and 4th arguments in start_operation. I have a couple of importers in my plugin, and they import text based files. The only entities the files describe are faces, and meta-data associated with them. The faces are described with one loop/array of points. Loops with a repeated point seemed to cause errors when used in add_face. I tried for quite a bit to get the Sketchup operation methods (abort_operation) to deal with it and gave up. Hence, my suggestion.

The only other option would be for your classes to keep references to all of the entities they create, and have the ‘undo’ code delete them. If instead of creating entities, your code is changing them, that’s really messy…

Greg

From where ? This is not one of the SketchUp API classes.
Is it from one of the example scripts ?

Please edit your previous posts (click the pencil icon,) and re-format them so they are readable.

Parametric is an example script supplied by Google then Trimble. It allows you to create complex 3d objects using a dialog box. The extension su_windows comes with Sketchup 2015 and uses the Parametric class as a base class. My script WinDoor creates parametric windows much like su_windows. WinDoor uses a class called OpeFMS_Param_Base which makes casement windows and also doors, then WinDoor combines them into door/window assemblies. Apart from not being able to undo everything in one go it works fine. I use it a lot. I would like to get it registered as an extension but unless I can get it to undo in one go that probably won’t happen. I can create and undo in one operation so long as i don’t edit an object.That makes lots of undo’s. I have tried model.start_operation/commit_operation in a hundred ways but the best I can get it to is an undo for each object created plus the final component insert. So if your window/door combination has say 2 windows and a door that makes 4 undos. Edits of already created WinDoors undo in one.

I think the problem revolves around the fact that model.start_operation/commit_operation cannot be nested. When WinDoor which is based on Parametric calls several objects from class OpeFMS, also based on the Parametric class, into being and the createWinDoor method has a model.start_operation/commit_operation set of method calls in it and the Parametric class also has a set of model.start_operation/commit_operation calls in it then you simply cannot get around nesting. I got it down to as few undos as possible by trial and error.

Greg suggested using a flag which I will look into in due course, but i think that would mean changing Parametric somewhat.

I just tried out Greg’s flag idea and it works! I thought it would when he suggested it and so thanks again Greg, great idea.
It changes Parametric so I’ve renamed it. I made a few other changes while I was at it but the biggest one was adding a method to set a flag for the model.start_operation/commit_operation method calls and then using it to exclude nested model.start_operation/commit_operation’s.

I need to tell you that many of those old examples use poor programming practice, that will not pass the Extension Warehouse inspection. (Yes ironic when they themselves are distributed via the warehouse.)

Only Ruby base classes (and special global API classes) should be defined at the toplevel.

Your custom parametric class should be defined within your unique author toplevel namespace module. (Whatever the name is you have chosen.)

i have been working on the requirements of the Extension Warehouse team and have the WinDoor scripts just about ready to re-submit. All my classes including the modified Parametric class are defined within my own namespace.The biggest problem has been getting everything to undo in one step.

For amateur programmers like myself it would be great if Trimble issued some example code that showed the basics of how they want extensions to be constructed. Ping-ponging code to the Extension Warehouse team could be a bit slow and frustrating compared with learning what’s required from some well written examples. The E W team could then point people at the relevant example code if they submitted anything that broke the rules.

5 Likes