End of input error

I keep getting this error…what am I doing wrong?

Error

Error: #<NameError: undefined local variable or method `sketchup_extension’ for CaydenWilson::ProTrim:Module>

:22:in `' :10:in `' :9:in `' SketchUp:1:in `eval'

Code

#Loading in handles
model=Sketchup.active_model
selection=Sketchup.active_model.selection
entities=Sketchup.active_model.entities
materials=Sketchup.active_model.materials
layer_array=Sketchup.active_model.layers

require "sketchup.rb"

module CaydenWilson
  module ProTrim

    #Input to gather data for trim
    prompts=["Trim Height:            ", "Trim Thickness:", "Quarter Round:", "R:", "G:", "B:"]
    defaults=[5.0,0.5,"No",255.0,255.0,255.0]
    list=["","","Yes|No","","",""]
    input=UI.inputbox prompts, defaults, list, "ProTrim"
      height,thickness,quarter_round,red,green,blue=input
    if quarter_round=='Yes'
      quarter_round_width=0.75
      quarter_round_height=0.75
    end
    notification=UI::Notification.new(sketchup_extension, "Select the trim path.")
    notification.show

    def get_points(axis, height, thickness)
      case axis
      when X_AXIS
        [ [0,0,0], [0,0,height], [thickness,0,0], [thickness,0,height] ]
      when Y_AXIS
        [ [0,0,0], [0,0,height], [0,thickness,0], [0,thickness,height] ]
      end
    end

    def create_trim_material(materials)
      trim_material=Sketchup.active_model.materials.add "Trim"
      trim_material.color=[red, green, blue]
      trim_group.material=trim_material
    end

    def build_trim(axis, selection, height, thickness)
      pts=get_points(axis, height, thickness)
      #Creating a face using array
      face=entities.add_face(pts)
      edges = face.edges
      connected=face.all_connected
      face.back_material = "Trim"
      material = trim.back_material
      face.material = trim
      #Selecting edges (path) to extrude on
      face.followme( selection.grep(Sketchup::Edge) )
    end

    def create_trim_layer(layer_array, entities)
      new_layer=model.layers.add ("Trim")
      model.active_layer=new_layer
      name=trim
      visable=True
    end
  end
end

add end to close the top module…

john

1 Like

I did before…it still throws me an error…

There’s no end for the if statement near the top.

1 Like

move the handles into the module?

john

1 Like

Now I’m getting variable errors when I run it…any idea? Inputbox appears and data is able to be imputed, but once ok is clicked I get these errors

Error: #<NameError: undefined local variable or method `sketchup_extension’ for CaydenWilson::ProTrim:Module>

:22:in `' :10:in `' :9:in `' SketchUp:1:in `eval'

Doesn’t seem to be an issue…but I’ll try it! :wink:

UI.Notification is designed to be used with reference to a SketchupExtension object. The first argument to UI.Notification.new (named sketchup_extension) must be a reference to a SketchupExtension object that your code has created and registered with the Sketchup engine. Consult the API documentation about SketchupExtension.

Side note: to avoid the forum markup engine mangling error messages, put triple backtick on the lines before and after where you paste the error messages. The < and > characters otherwise are taken as markup tags and create a mess.

2 Likes

Perfect! Didn’t know I should do that.

By the way, I agree with @john_drivenupthewall that the “handles” should be inside the module, not at the global level. You never know when global variables will collide with ones someone else defined!

Best they would be in a method. You won’t be able to access these local variables anyways from other methods.

Create a method to wrap all orphan code.

module CaydenWilson
  module ProTrim
    def init
      model = Sketchup.active_model # Use spaces around operators
      selection = model.selection # Use the same model reference
      # …
      prompts = ["Trim Height:            ", "Trim Thickness:", "Quarter Round:", "R:", "G:", "B:"]
      # …
      notification.show
    end
    # …
    self.init # Instead of calling it here, you can do this in a menu item's command handler.
  end
end

Then call the method.

Now you have more flexibility and control to run this code when you need it. You probably don’t want to force this notification onto the user when SketchUp starts.

1 Like

Yes, and I’ve told Cayden previously not to use UI::Notification objects.
They are not designed to be used for workflow prompts (and their display time cannot be controlled [yet.])
Cayden should use normal modal messageboxes instead.

(Cayden, FYI, modal means that the messagebox or dialog seizes the focus and does not release it until the user dismisses the window.)


With regard to the SketchupExtension object, it’s registrar file is a separate file that goes in the “Plugins” folder. All your extensions other files go in a subfolder that has the same name as the registrar file.
If the extension registrar file is "CaydenWilson_ProTrim.rb" then your extension subfolder must be named "CaydenWilson_ProTrim".

The handles are not an issue because your code is not using them.

The most prime rule for coding in a shared ObjectSpace is that ALL YOUR CODE MUST be within your toplevel namespace module (ie, the CaydenWilson module in the above example,) and any code specific to a CERTAIN EXTENSION should ALL be within that extension’s submodule (in the example the CaydenWilson::ProTrim submodule.)

Even the lonely …

require "sketchup.rb"

… method call can be within your modules. It is a frivolous call because the SketchUp engine loads it (and the other "Tools" subfolder files "extensions.rb" and "langhandler.rb") during the startup cycle ever since the release of SketchUp 2014. These loads happen before any extensions ever begin to load.

You do not “run” code like this (ie, a module definition) repeatedly in an event-driven programming environment. Instead the module is loaded at startup and waits for an event to fire off the display of the inputbox.

The event will be the user either choosing a command from a menu or clicking a toolbar button. So this means you need a “eval once” block at the bottom of your module that creates the UI elements (your extension’s submenu and /or toolbar and the command objects that will be attached to the menu items and toolbar buttons.)

    if !@loaded
      submenu = UI.menu("Extensions").add_submenu("ProTrim")
      cmd = UI::Command.new("Build Trim") { build_trim_command() }
      submenu.add_item(cmd)
      # Set the @loaded var to true so this block is only evaluated ONCE:
      @loaded = true
    end

Such a block that evaluates only once, will prevent multiple submenus, menu items and toolbars, etc., from getting created, if you tweak your code and need to reload it during development.

Reference class documentation for:


So as shown in the eval once block above, you need a command method that will be fired by the user when they start your command. I called it "build_trim_command()"but you can name it what you like.

Put the inputbox code inside this method.

Also I’ve told you repeatedly, you must always check that the return value from the input box is “truthy” (ie only two things in Ruby are falsehoods and these are false and nil. Everything else evals booleanwise as a truth.)
So this means that if the user cancels the inpubox then the variable input will eval as a falsehood.
If the user enters values (or accepts the default values) and clicks OK, then input will be an array instance and eval as a truth, so your subsequent code can go ahead and treat it as an array of values.
A simple one liner inserted just after the call to UI.inpubox would be …

return unless input

It tests that input evals as true (and therefore cannot be false or nil,) and so must be an array instance. If the user has cancelled the inputbox, your code should assume the user wishes to cancel the command, and so the return statement will exit the method, and nothing will happen.

Some extensions do display a “Command Cancelled” messagebox, just so the user knows their cancel has worked and they know nothing in they model changed. (Your code, your choice.)


Lastly, in order for your methods to call each other without qualification, you need to extend the module with itself. Put this statement at the top of the module.

extend self
1 Like

Sorry about the notification being in there…I edited the file and forgot to save it, and then didn’t fix that. I think I’ve fixed the code to what you’ve been saying…I just need to figure out how to execute the face build/follow me if the user clicks OK in the messagebox. Also, I keep getting an error on my toolbar section…and my icons do not appear on my toolbar or as an option to add to my toolbar. Thanks for your help!

module CaydenWilson
  module ProTrim

    extend self

    def init
      #Loading in handles
      model=Sketchup.active_model
      selection=Sketchup.active_model.selection
      entities=Sketchup.active_model.entities
      materials=Sketchup.active_model.materials
      layer_array=Sketchup.active_model.layers

      require 'sketchup.rb'
      require 'extensions.rb'

      pro_trim=SketchupExtension.new('ProTrim", "ProTrim/base.rb"')
      pro_trim.version='1.0'
      Sketchup.register_extension(pro_trim, true)

      self.init
    end

    def get_points(axis, height, thickness)
      case axis
      when X_AXIS
        [ [0,0,0], [0,0,height], [thickness,0,0], [thickness,0,height] ]
      when Y_AXIS
        [ [0,0,0], [0,0,height], [0,thickness,0], [0,thickness,height] ]
      end
    end

    def create_trim_material(materials)
      trim_material=Sketchup.active_model.materials.add "Trim"
      trim_material.color=[red, green, blue]
      trim_group.material=trim_material
    end

    def build_trim(axis, selection, height, thickness)
      #Input to gather data for trim
      prompts=["Trim Height:            ", "Trim Thickness:", "Quarter Round:", "R:", "G:", "B:"]
      defaults=[5.0,0.5,"No",255.0,255.0,255.0]
      list=["","","Yes|No","","",""]
      input=UI.inputbox prompts, defaults, list, "ProTrim"
      return unless input
        height,thickness,quarter_round,red,green,blue=input
      if quarter_round=='Yes'
        quarter_round_width=0.75
        quarter_round_height=0.75
      end
      message="Select edges for your trim path."
      result=UI.messagebox(message, MB_OKCANCEL)
      if result==IDCANCEL
        return
      end
      pts=get_points(axis, height, thickness)
      #Creating a face using array
      face=entities.add_face(pts)
      edges = face.edges
      connected=face.all_connected
      face.back_material = "Trim"
      material = trim.back_material
      face.material = trim
      #Selecting edges (path) to extrude on
      face.followme( selection.grep(Sketchup::Edge) )
    end

    def create_trim_layer(layer_array, entities)
      new_layer=model.layers.add ("Trim")
      model.active_layer=new_layer
      name=trim
      visable=True
    end
    if !@loaded
      submenu = UI.menu("Extensions").add_submenu("ProTrim")
      toolbar=UI::Toolbak.new "ProTrim"
      cmd = UI::Command.new("Build Trim") { init() }
      cmd.small_icon="IconSmall.png"
      cmd.large_icon="IconLarge.png"
      cmd.menu_text="ProTrim"
      toolbar.add_item cmd
      toolbar.show
      submenu.add_item(cmd)
      # Set the @loaded var to true so this block is only evaluated ONCE:
      @loaded = true
    end
  end
end

Once again you are listening … reread what was written.

1 Like

There is NO class named UI::Toolbak … it’s UI::Toolbar.

You may need to use absolute paths to the icon files, or relative paths from the Plugins folder. I can’t remember which.

The Ruby global function __FILE__ returns the absolute path to the file it is called from.
The Ruby global function __dir__ returns the absolute directory path where the file it’s called from is located.
So if the icon files are in the same folder …

cmd.small_icon = File.join(__dir__,"IconSmall.png")

If they were in an "images" subfolder from where the file is …

cmd.small_icon = File.join(__dir__,"images","IconSmall.png")

cmd = UI::Command.new("Build Trim") { init() }

Andreas gave you some general advice that does not apply in this case.
You don’t need anything in the init() method. Delete it.

And (as said above) the following …

module CaydenWilson
  module ProTrim
    pro_trim = SketchupExtension.new('ProTrim", "CaydenWilson_ProTrim/base.rb"')
    pro_trim.version = '1.0'
    pro_trim.description = 'Professioanl Trim Building extension.'
    pro_trim.creator = 'Cayden Wilson'
    pro_trim.copyright = '2020'
    Sketchup.register_extension(pro_trim, true)
  end
end

… all goes into a registrar file named "CaydenWilson_ProTrim.rb" outside your extension subfolder, which must be named "CaydenWilson_ProTrim".

The registrar file gets installed into the “Plugins” folder, and is automatically run by SketchUp at startup to register your extension.

1 Like

Do not insert spaces between a method name and it’s argument list. This will usually cause a warning.

There is no good reason for Ruby code to change the active layer (tag). ALL geometric primitives should always be untagged (associated with “Layer0”.) Only the trim groups or components should be assigned to other layer/tags.


Also you are not helping the interpreter or US to read your code by omitting spaces before and after operators.

1 Like

Once the method is called you have infinite recursion… but it is never called.

1 Like

I fixed all the spaces for better readability…Its been a bad habit of mine not using them previously. Also, I am going to look into creating a group for the trim, which has always been my intention.

When you say I don’t need anything in the init method, do you mean to delete the handles or do you mean to just remove the init method?

Yes remove the whole method. Why would you need a method if it’s not needed or it’s empty of code ?

1 Like