sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Model Operation blockform methods.

    Scheduled Pinned Locked Moved Developers' Forum
    24 Posts 4 Posters 2.0k Views 4 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Dan RathbunD Offline
      Dan Rathbun
      last edited by

      Last night I pushed the first commit of the SAE::Operation class to a Public BitBucket Mercurial repo.

      I haven't yet got much chance to test it much.

      Check it out:
      https://bitbucket.org/dan_rathbun/sae-operation-class

      Clone the repo:
      ssh;//hg@bitbucket.org/dan_rathbun/sae-operation-class

      [floatr:biungwe1]bitbucket_get_source_popup.PNG[/floatr:biungwe1]ADD: If you do not have a Mercurial client like TortoiseHg installed, you can grab an archive using the "get source" popup menu, at the top right:


      Line 8 of file header, is a boo-boo, should read:
      " %(#008000)[# idea by Daniel Bowring]"
      😳

      I'm not here much anymore.

      1 Reply Last reply Reply Quote 0
      • danielbowringD Offline
        danielbowring
        last edited by

        Fairly late response, but here's my thoughts/responses:

        @dan rathbun said:

        here

        1. This is one advatage of a fallback system - the operation is aborted and then the error is allowed to propogate

        2. I believe the error should continue to be raised - an exception isn't (normally) what you're expecting, so execution should halt.

        3. My suggested nomenclature isn't set in stone. The reason I chose this is due to the duality of the object it may reference (the current operation for appending, or the (externally identical) interface for starting a new operation

        4. #abort aborts the operation as it is operating - erase! removes it after the fact. It should be noted erase can only be performed on the "most recent" operation (erasing any other doesn;t make sense)

        5. This I agree with - the handle would already be available (as the most recent operation, and as the argument to the block). The value returned should be the result of the block

        [6]) [which model to execute on]
        Ah, something I didn't think out very well - I think passing the active model is a good way, but I think the user should also be able to pass arguments, ie
        op.call(Sketchup.active_model, arg1, arg2, ...)
        to allow the user to perform operations on entities.
        But then again, when would you ever want it on anything other than the active model?

        @dan rathbun said:

        here

        Nesting means you don't have to worry if code your calling has its own operation. Some API operations, for example, will litter the undo stack.
        If you don't want to support this, you can just have calls to start a new operation do "nothing" except execute the block and return the value - the downside to this is you lose the ability to abort (with rollback).

        @dan rathbun said:

        here

        #erase! would only be applicable to the most recent (ie, top of the stack). Anything else would throw an error.
        The "code fantasy" is just that - you wouldn't nest operations "directly" like that - Imagine the operation is a method, the code becomes
        def root() #... child() #... end def child() #... end
        You can simulate this behaviour using clever transparency flags, or by ignoring the child operation (as noted before, with its disadvantage)

        @thomthom said:

        However I've also had tools where start and commit happens in different Tool events - block operations would not work there.

        Operations over different tool events? I was under the impression you had to finish your operation before control returned to sketchup (not that I ever tested, I have just assumed)
        Given this anyway, you could just keep "appending" to the previous operation by keeping its instance around (see Equiv. code in the description document) (or since its within the lifetime of the tool, even just using model.last_operation)

        1 Reply Last reply Reply Quote 0
        • Dan RathbunD Offline
          Dan Rathbun
          last edited by

          Issue: Nesting Operations

          @unknownuser said:

          @unknownuser said:

          (http://forums.sketchucation.com/viewtopic.php?p)":1e6klaes]Does anyone here, actually NEST operations within each other ?? (... or is this just asking for trouble?)

          It seems frivolous, the nested code might just as well be a begin .. end block, or a method call.

          You cannot nest operations. If you begin with a start_operation before an previous one has committed you interfere with the whole operation. I think the first one will commit - not sure what happens to the remaining actions.

          **%(#BF4000)[I tested this. In reality NOTHING gets committed.

          puts() statements will output to the console, but ALL model modification statements are ignored (or perhaps automatically aborted.)]**

          @unknownuser said:

          Nesting means you don't have to worry if code your calling has its own operation. Some API operations, for example, will litter the undo stack.

          There is no way for me to parse or detect whether methods (or procs,) have an operation within them.

          (Unless I hook the API's three operational methods, which are a HUGE no-no. It is a violation of my number 1 law for the SAE utilities, and has now become an explicitly stated violation of the Trimble API Terms of Service. Google was not interested that much in the SketchUp API, as they were not a "Rubyist" company, and were more interested in the native integration of SketchUp with certain Google applications and services. Trimble has the opposite viewpoint, since they have announced their intention to provide professional plugins for SketchUp, and enhance their other applications with the SketchUp engine. So expect Trimble to be much more aggressive in protecting the integrity of the API.)
          Again, I am opposed to showing, implying, and actually allowing coders to get the idea that nesting operations is something valid.

          It goes against Ruby Best Practices, and is contrary to the LIFO sequential nature of the Undo/Redo stacks.

          @unknownuser said:

          If you don't want to support this, you can just have calls to start a new operation do "nothing" except execute the block and return the value - the downside to this is you lose the ability to abort (with rollback).

          Not necessarily true.

          A method call or statement, within a operation block, can always call model.abort_operation() (which is one good reason to always pass the model reference into the block.)

          @unknownuser said:

          ... when would you ever want it on anything other than the active model?

          In an MDI interface, operations CAN be performed on the other open models.

          There are plugins that use another open model as a "scratch pad" to create components and save them out to a collection, or have some kind of co-ordination between two open models. (This can only be done on the Mac currently, of course.)

          I'm not here much anymore.

          1 Reply Last reply Reply Quote 0
          • Dan RathbunD Offline
            Dan Rathbun
            last edited by

            Issue, the term: "Child" Operations

            I am going to remove any reference to operations being a "child". (It is proving problematic.)

            In reality, the C-side undo/redo engine is the parent of ALL operations.
            Parents (usually,) produce children in a sequential, one-by-one manner.

            Operations as recorded on the undo & redo stacks are actually siblings of each other.

            Appending an operation to it's elder sibling, is more analogous to conjoined twins, than a parent-child relationship.

            ~

            I'm not here much anymore.

            1 Reply Last reply Reply Quote 0
            • danielbowringD Offline
              danielbowring
              last edited by

              @dan rathbun said:

              ....stuff...

              I think you're overlooking that I wrote this not to be implemented in ruby, but as something to be implemented in future Sketchup Versions.
              I too have tried to implement a "perfect" way of doing this in ruby, and the same pitfalls (of course) arise.

              1 Reply Last reply Reply Quote 0
              • Dan RathbunD Offline
                Dan Rathbun
                last edited by

                @unknownuser said:

                I think you're overlooking that I wrote this [suggestion for an Operation class] not to be implemented in Ruby, but as something to be implemented in future Sketchup Versions.

                No not overlooking that.

                Of course it MIGHT be easier for the Trimblers to detect when a start_operation has been called a 2nd time, before the first has been committed, and ignore the 2nd.

                But I think the pitfall is that they will not find a consensus on whether they should ignore the next commit they "see" (assuming it's is paired with the 2nd start_operation,) and "hope" for getting the "outer" commit.

                I suspect that it will prove to be too confusing (if that's the correct word,) to attempt to compensate for poor programming practice.

                I'm not here much anymore.

                1 Reply Last reply Reply Quote 0
                • danielbowringD Offline
                  danielbowring
                  last edited by

                  @dan rathbun said:

                  No not overlooking that.

                  Of course it MIGHT be easier for the Trimblers to detect when a start_operation has been called a 2nd time, before the first has been committed, and ignore the 2nd.

                  But I think the pitfall is that they will not find a consensus on whether they should ignore the next commit they "see" (assuming it's is paired with the 2nd start_operation,) and "hope" for getting the "outer" commit.

                  I suspect that it will prove to be too confusing (if that's the correct word,) to attempt to compensate for poor programming practice.

                  And that's the advantage of using code blocks - by definition they have a defined start and end πŸ˜„
                  If you we're going for support, you'd have to assume that the scripter got it right - you shouldn't try to solve every problem they could cause. If an error is the result, that's because there was an error to begin with.

                  1 Reply Last reply Reply Quote 0
                  • Dan RathbunD Offline
                    Dan Rathbun
                    last edited by

                    @unknownuser said:

                    And that's the advantage of using code blocks - by definition they have a defined start and end πŸ˜„
                    If you we're going for support, you'd have to assume that the scripter got it right - you shouldn't try to solve every problem they could cause. If an error is the result, that's because there was an error to begin with.

                    OK. (Head spinning!)

                    That's what I said.

                    Then you made the argument that the Operation's block should be able to call methods that could have an operation in them (making a nested operation, which is invalid.)

                    The main problem we have with the API is that these invalid operations, just fail silently, when they should raise a ScriptError (or some other exception.)

                    I'm not here much anymore.

                    1 Reply Last reply Reply Quote 0
                    • Dan RathbunD Offline
                      Dan Rathbun
                      last edited by

                      Weird...

                      If you specify an empty string, as the name of the first operation, in a set which the followers are transparent,... the name in the menu is: "Undo Macro"

                      I'm not here much anymore.

                      1 Reply Last reply Reply Quote 0
                      • Dan RathbunD Offline
                        Dan Rathbun
                        last edited by

                        I pushed several changes today.
                        https://bitbucket.org/dan_rathbun/sae-operation-class

                        Works much more like it should:

                        • call/ commit method can take an array of values to pass into the block.
                        • class method ::raise_errors() and ::raise_errors=() to control whether exceptions are raised. (This will likely be used for debugging mostly.)
                        • class method ::redo wrapper for Sketchup.send_action('editRedo:')
                        • class method ::undo wrapper for Sketchup.send_action('editUndo:')
                        • an OpStat object is passed into the block, holding information about the operation itself that might be useful. (This was done instead of passing a direct reference to the operation itself, which would open the door to dangerous things, like calling commit from within the block and causing a vicious loop that would probably cause a stack overflow.)
                        • onError() {|opStat,model,exception| ... code block ... }

                        Allows custom handling of errors (ie, translate error messages into Spanish, etc.)

                        • onSuccess() {|opStat,model,results| ... code block ... }

                        Allows custom handling if everything goes well. (ie, prompt the user to save something, etc.)

                        I'm not here much anymore.

                        1 Reply Last reply Reply Quote 0
                        • Dan RathbunD Offline
                          Dan Rathbun
                          last edited by

                          Here's an example of using the one-shot block form class wrapper method:

                          1) Say you write a simple "Add Face" plugin with no operations (because either you did not know about them being a newbie, or you just wished to get it working first and polish it later.)

                          So you have code that looks like:

                          require('sketchup.rb')
                          
                          module Author ### <-- this would actually be your toplevel namespace
                          
                            module FaceAdder
                            
                              class << self
                                private
                                def add_face
                                  #
                                  mdl = Sketchup.active_model
                                  depth = 10
                                  width = 10
                                  pts = []
                                  pts[0] = [0, 0, 0]
                                  pts[1] = [width, 0, 0]
                                  pts[2] = [width, depth, 0]
                                  pts[3] = [0, depth, 0]
                                  # Add the face to the entities in the model
                                  grp = mdl.entities.add_group()
                                  face = grp.entities.add_face( pts )
                                  return grp
                                  #
                                end
                              end # proxy class
                              
                              unless file_loaded?(File.basename(__FILE__))
                                UI.menu('Plugins').add_item('FaceAdder') {
                                  add_face()
                                }
                                file_loaded(File.basename(__FILE__))
                              end
                          
                            end # module FaceAdder
                          end # module
                          
                          

                          2) Now you want the the entire command to be one Undo operation.

                          You can modify the menu item proc, like this:

                                UI.menu('Plugins').add_item('FaceAdder') {
                                  begin
                                    require('sae/operation.rb')
                                  rescue LoadError
                                    add_face() # User does not have file.
                                  else
                                    SAE.operation('Add Face') { add_face() }
                                  end
                                }
                          
                          

                          In this example, the "Undo" name that will appear in the menu (or the popup tooltip for the undo button on the toolbar,) is specified as "Add Face". All other options will use the defaults, .. ie, the 2nd model argument will default to the active_model, the 3rd argument will be set to disable the UI during the operation's block execution, and the operation will not be hidden.

                          Putting it all together, the example file, would look like:

                          require('sketchup.rb')
                          
                          module Author ### <-- this would actually be your toplevel namespace
                          
                            module FaceAdder
                            
                              class << self
                                private
                                def add_face
                                  #
                                  mdl = Sketchup.active_model
                                  depth = 10
                                  width = 10
                                  pts = []
                                  pts[0] = [0, 0, 0]
                                  pts[1] = [width, 0, 0]
                                  pts[2] = [width, depth, 0]
                                  pts[3] = [0, depth, 0]
                                  # Add the face to the entities in the model
                                  grp = mdl.entities.add_group()
                                  face = grp.entities.add_face( pts )
                                  return grp
                                  #
                                end
                              end # proxy class
                              
                              unless file_loaded?(File.basename(__FILE__))
                                UI.menu('Plugins').add_item('FaceAdder') {
                                  begin
                                    require('sae/operation.rb')
                                  rescue LoadError
                                    add_face() # User does not have file.
                                  else
                                    SAE.operation('Add Face') { add_face() }
                                  end
                                }
                                file_loaded(File.basename(__FILE__))
                              end
                          
                            end # module FaceAdder
                          end # module
                          
                          

                          If this kind of method were to be added into the API, it would likely be an instance method of Sketchup::Model, which would not need the 2nd model argument.

                          Of course there could also be a model "flexible" duplicate class method (like I've implemented it,) that does take a model arg, which would either be a module method Sketchup.operation(), or a class method like Sketchup::Model.operation()

                          Thoughts ??

                          I'm not here much anymore.

                          1 Reply Last reply Reply Quote 0
                          • Dan RathbunD Offline
                            Dan Rathbun
                            last edited by

                            Putting this on the back burner until I get some feedback.

                            I'm not here much anymore.

                            1 Reply Last reply Reply Quote 0
                            • 1
                            • 2
                            • 2 / 2
                            • First post
                              Last post
                            Buy SketchPlus
                            Buy SUbD
                            Buy WrapR
                            Buy eBook
                            Buy Modelur
                            Buy Vertex Tools
                            Buy SketchCuisine
                            Buy FormFonts

                            Advertisement