How to break, exit, abort, fail, raise or whatever in a Script

ruby
api

#1

exit, break, end, kill, die, abort, raise, rescue, abort_operation…???

Its hard to know what is the appropriate way to stop execution of a plugin script. I must be missing something because this seems so basic but yet could not find much on the topic here or in the wild.

There’s a detailed discussion here but there seems to be disagreement between Dan and Tig about if exit is the right approach or return nil.

There’s tons on plain old Ruby commentary like here but some of these methods bugsplat SU.

So… using the UI.inputbox as an example where you might want to give a user a side exit. I’m using abort_operation because it is at least mentioned in the API but it does not seem to work as expected because the subsequent code in the script WILL be executed.

def some_script()

    Sketchup.active_model.start_operation('Some Script', true)

    response = UI.inputbox("Question?", "", "Please tell me!")

    # catch "Cancel" button clicks
    if(response == false)
        Sketchup.active_model.abort_operation
    end

    #some code I would like to NOT run if Cancel has been selected
    print "What are you doing here?\n"

    #some more code I would like to NOT run if Cancel has been selected
    print "I said go away!\n"

    Sketchup.active_model.commit_operation

end #def

#2

#abort_operation tells the API to forget the actions so far in the active operation and cease capturing additional ones. It does not quit, exit, or return from your code.

I think the answer needs some context than “plugin script”. How was the Ruby invoked and what is it doing at the time?


#3

Uh… I’m not sure. Basically a plugin in the ‘plugins’ folder that gets called via a menu item or toolbar button in SU.

Here is a post that includes the full Module I’m working on. As you can see, using return nil at the moment but still not preventing subsequent code in the def from being executed.


#4

make it conditional with or without a return value…

def some_script

    Sketchup.active_model.start_operation('Some Script', true)

    response = UI.inputbox(["Question?", "", "Please tell me!"])

    # catch "Cancel" button clicks
    if(response == false)
      Sketchup.active_model.abort_operation
      return 'user canceled'
    else
      #some code I would like to NOT run if Cancel has been selected
      print "What are you doing here?\n"

      #some more code I would like to NOT run if Cancel has been selected
      print "I said go away!\n"

      Sketchup.active_model.commit_operation
    end
end #def

#5

Anyone that can build a plugin that aborts other plugins and vanilla tools, will make a fortune.


#6

OK, right BUT let’s say that “some other code” is like 4000 lines long. It stinks to have to indent all that code and track down that stray end to close the conditional when all you really want to do is just stop the whole deal right there. I was hoping for something like PHP die I guess. I know there is next unless but I think that is only for iterating enumerables.


#7
Sketchup.active_model.abort_operation && return if(response == false)

#8

or roll your own :die

def die
   Sketchup.active_model.abort_operation
   'user canceled'
end

    #in your other def...
    # catch "Cancel" button clicks
    return die  if(response == false)

john


#9

I guess I am not understanding this line

Sketchup.active_model.abort_operation && return if(response == false)

It seems very similar to

if(response == false)
    Sketchup.active_model.abort_operation
    return 'user canceled'
end

… and what is that return 'user canceled' ? It just seems like you are returning an string for like a placeholder or something (i.e. not outputting to console or input box or anything).


#10

Not all of them exist, and those that exist are used for different purpose:

  • Ruby language:

    • commonly used:
      • return is a keyword that leaves the current method and give control back to the calling method on the method stack (it does not also return from the previous method). This is commonly used, but make sure you know what object (or nil) your method returns because the calling method must be able to handle it.
      • raise throws an exception and immediately leaves the current method including all calling methods until a method can handle the exception. You use it if you noticed your program is in an invalid state where it cannot continue (e.g. when you are given “3” and “b” and are supposed to add them, this is undefined). It is part of the pattern:
    • break is a keyword and leaves a loop (only one level), or a Proc (lambda function).
    • end is a keyword that ends a method or class or block definition. It does not end a running ogram.
    • exit is a method that quits the Ruby interpreter. This is something you usually do not want to do (although in SketchUp it has no effect).
  • SketchUp-specific:

    • Sketchup::Model#abort_operation: only if you have started an undoable operation (for the Edit → Undo/Redo menu) with start_operation and you do not plan to finish that operation with commit_operation (due to invalid user input or an error), you abort that operation so that SketchUp does not record it for the undo stack.

#11

I have seen and worked with such code, but generally when designing code there is no legitimate reason why “some other code” should be allowed to be that long. A well defined method is 20 lines long, and calls other methods for the rest of work.

Indentation and code formatting is better done (enforced) in a code editor that reformats every time when you save.

If nesting levels cause confusion you could use the early exit strategy (one can argue whether it makes code more understandable):

if condition
  # some code
  return
else
  # other code
end

is equivalent to

if condition
  result = some_code() # external method
  return result
end
# other unindented code

or

return some_code() if condition
# other unindented code

#12

it will output to console to let you know what happened when testing…

did you run it?

I left it out of the shorthand version as it needs () when used before a conditional…

Sketchup.active_model.abort_operation && (return 'user canceled') if response == false

john


#13

Oh cool. So return 'hey' is like print 'hey'? I see.


#14

Yes, I understand - things should be more targeted than having 4000 lines of code. Just a hypothetical. So you are saying you have worked with code like that? Seems cumbersome but I hear you.

I like John’s idea of a custom die that would be an exit valve which I’ll respond to separately.


#15

This roll your own and a none def version “seems” to work BUT… it crashes SU on my system. It happens only after a few minutes with no bus splat. Just a system message (I’m on Mac).


#16

try ‘my_die’ just incase it is that…

but it ran fine on my mac, with out your other code…

john


#17

Actually return does not output to console on a Mac I guess. Does not work output anything.

There must be some differences with the console logging on Mac. As I noted at:

https://sketchucation.com/forums/viewtopic.php?f=323&t=17587&start=120

But no one responded so maybe I’m the only one with the issue.

I need to use print rather than put for some reason.


#18

you are trying puts with the s ???

I only ever use mac’s, and I only use Ruby Console [not the ‘webDialog versions’]…

in all my Ruby code I use puts when I want a \n or p when I don’t…

in SU v17 all of the stdout handling was changed and you may find details of in some older SCF threads…

at the time I added an :echo method and did a find and replace on scripts that change broke…

    def echo(msg)
      SKETCHUP_CONSOLE.write("#{msg}\n") if SKETCHUP_CONSOLE.visible?
    end

I haven’t noticed an issue with puts in v18, the only time it affects me now if when I use Threads, which really is frowned upon…

john


#19

Yes, sorry I means puts and I don’t get it either! (video below):


#20

@slbaumgartner is on Mojave, I’m not…

you want my version of RC, as seen in my gif…

john