Errors in Ruby Console

I’m getting some errors in the Ruby Console for my code…one on line 81 for a \n (new line) syntax and on line 185 where SketchUp states there should be a } instead of a : which I have there so that the program can enable or disable the given command ("Create Trim"). I’ve tried changing these areas to SketchUp’s suggestion just to see what happens (If I actually did the code wouldn’t do what I want) and just get more frustrating errors. Any and all advice is appreciated! I’ve been working on this tirelessly and am hoping I can start polishing it up and working on any bugs it may have. Also, if things look messed up…I had to space down some lines to make them fit (so you don’t need to scroll left to right to read code). Thank you so much! :grinning: :wink:

#--------------------------------------------------------------------------

require File.join(File.dirname(__FILE__), 'CaydenWilson_ProTrim.rb')

#--------------------------------------------------------------------------
module CaydenWilson
  module ProTrim

    def prompt_user_selection
      selection = Sketchup.active_model.selection
      #if faces or groups are in selection
      #alert user to try again and close program
      if !only_edges_selected?(selection)
        UI.messagebox('Your selection cannot include faces or groups.
          Please select a trim path using edges only.' , MB_OK)
        return
      end #if

      paths = collect_paths_from_selection
      #if no selected path exists, alert user to retry and close program
      if !paths
        result = UI.messagebox('There is no selected path.
          Please select a trim path using edges only.' , MB_OK)
        return false if result = IDOK
      end #if
    end #prompt_user_selection

    def model_trim_command()
      trim = get_trim_choice(input)
      return unless trim #close if user cancels program
      trim_component_def = load_trim_component(trim)

      #If .skp file unretreiveable, alert user and close program
      if !trim_component_def
        result = UI.messagebox('File cannot be loaded.
          Please select a different trim profile.' , MB_OK)
        return false if result == IDOK
      else
        result = extrude_trim_profile(trim_component_def, paths)
        #Alert user modeling was successful and close program
        if result
          result = UI.messagebox('Trim created successfully.' , MB_OK)
          return false if result == IDOK
        end #if
      end #if/else
    end #model_trim_command

    def get_trim_choice()
      #Prompt user to choose trim profile from drop down menu
      prompts = ["Select your trim profile from the drop down menu: "]
      defaults = ["trim.skp"]
      trimlist = [trim_name_data]
      fields = [trimlist.join('|')]
      input = UI.inputbox(prompts, defaults, trimlist, fields,
        "Select Trim Profile")
      return unless input
      trim = input[0]
    end #get_trim_choice

    def load_trim_component(input)
      @profiles_path = File.join(__dir__, 'Profiles')
      #"Profiles" subfolder of this file's folder
      if Dir.exist?(@profiles_path)
        Dir.chdir(@profiles_path) {
          @trimfiles = Dir['*.skp']
        }
      else
        Dir.mkdir(@profiles_path)
      end #if/else

      #Creating a json file that has descriptions of
      #trim files reloaded into a hash
      require 'json'

      json = @trimhash.to_join
      #Write to disk
      filepath = File.join(@profiles_path, 'trim_profiles.json')
      begin
        File.write(filepath, json, mode: "w", encoding: "UTF-8")
      rescue =>
        UI.messagebox("There was an error with the file.
          Please choose a different trim profile.")
        puts err.inspect
      end #begin/rescue statement

      @trimhash = JSON.parse(json)
      @trimhash.keys
      @trimhash["trim_desc"]
    end #load_trim_component

    def get_trim_material_data()
      #Prompt user to choose trim profile from drop down menu
      prompts = ["Material Name:       ", "HEX Value: "]
      defaults = ["", ""]
      list = ["", ""]
      input = UI.inputbox(prompts, defaults, list, "Choose Trim Color")
      return unless input
      material_name = input[0]
      material_color = input[1]
    end #get_trim_material_data

    def create_trim_matreial(material_name, material_color)
      materials = Sketchup.active_model.materials[0]
      materials.each {|material| if material.display_name == material_name
        materialExists = true
      else
        materialExists = false
      end #check for material
      }

      #If material nonexistant create and add to pallet
      if materialExists == false
        #creating material
        material = materials.add material_name
        material.color = material_color
      end #if
    end #create_trim_material

    def extrude_trim_profile(trim_component_def, paths)
      @entities = Sketchup.active_model.active_entities
      #Explodes loaded component
      group = nil
      undo(mode, "Create New Trim")do
        group = entities.add_group #Added at orgin
        instance = group.entities.add_instance(trimdef, IDENTITY)
        group.transform!(transgroup)
        instance.explode
        face = group.entities.grep(Sketchup::Face).first
        face.followme(edges)
      end #undo
      return group

      #Preparing face for extrusion
      connected = face.all_connected
      face.material = trim
      face.back_material = trim
      #Extrudes trim profiles along path(s)
      face.followme(paths.grep(Sketchup::Edge))

      #Alerts user modeling is done
      result = UI.messagebox('Your trim profile was created successfully.' ,
        MB_OK)
      return false if result == IDOK
    end #extrude_trim_profile

    def import_custom_profile()
      custom_path = get_new_profile_path()
      if custom_path
        require 'filetutils' unless defined? FileUtils
      begin
        FileUtils.cp(custom_path, @profiles_path)
      rescue IOError => err
        UI.messagebox("Error copying profile to folder.\n#{err.message}")
      else
        puts "Profile Copy Cancelled"
        return
      end #if/else/if...
    end #import_custom_profile

    def help()
      dialog = UI::HtmlDialog.new({
        :dialog_title => "ProTrim Help and Support",
        :scrollable => true,
        :resizable => false,
        :width => 1080,
        :height => 720,
        :left => 500,
        :top => 500,
        :style => UI::HtmlDialog::STYLE_UTILITY
        })
        dialog.set_url("https://caydenwilson017.wixsite.com/cwdesigns1")
        dialog.show
      end #dialog
    end #help
    #Checking if UI is loaded. If not, loads UI
    if !@loaded
      submenu = UI.menu("Extensions").add_submenu("ProTrim")

      tb = UI::Toolbar.new("ProTrim")

      cmd = UI::Command.new("Create Trim") {
        model_trim_command()
      }
      cmd.menu_text = "Create Trim"
      cmd.set_validation_proc {
        selection.empty? MF_GRAYED | MF_DISABLED : MF_ENABLED
      }
      UI.submenu("ProTrim").add_item(cmd)

      #Command that allows user to import their custom trim profiles
      cmd = UI::Command.new("Import Custom Profile") {
        import_custom_profile()
      }
      cmd.menu_text = "Import Custom Profile"
      UI.submenu("ProTrim").add_item(cmd)

      #Command directing user to support site
      cmd = UI::Command.new("Help") {
        help()
      }
      cmd.menu_text = "Help and Support"
      UI.submenu("ProTrim").add_item(cmd)
      @loaded = true
    end #@loaded
  end #ProTrim
end #CaydenWilson

At line 81 you have a string split over two lines.

        UI.messagebox("There was an error with the file.
          Please choose a different trim profile.")

Assuming that you want those to appear on separate line, you could put a newline
in the string with \n but you also need to fix the string being broken over two lines with

        UI.messagebox("There was an error with the file.\n"\
          "Please choose a different trim profile.")
      or
        UI.messagebox("There was an error with the file.\n" +
          "Please choose a different trim profile.")

Your help() method is nested inside your import_custom_profile method as you have a begin without an end. If you’d indented the lines you would have spotted it.

3 Likes

Also in #185 you test: selection.empty? But haven’t defined ‘selection’ !
Try using something like: Sketchup.active_model.selection.empty?

1 Like

Thank you!

That was after the error…I have the \n already written in the code here…

def import_custom_profile()
      custom_path = get_new_profile_path()
      if custom_path
        require 'filetutils' unless defined? FileUtils
      begin
        FileUtils.cp(custom_path, @profiles_path)
      rescue IOError => err
        UI.messagebox("Error copying profile to folder.\n#{err.message}")
      else
        puts "Profile Copy Cancelled"
        return
      end #if/else/if...
    end #import_custom_profile

How about here…

      cmd.menu_text = "Create Trim"
      cmd.set_validation_proc {
        Sketchup.active_model.selection.empty? MF_GRAYED | MF_DISABLED : MF_ENABLED
      }
      UI.submenu("ProTrim").add_item(cmd)

This was the original problem area…

In method load_trim_component()

Needs to be …

json = @trimhash.to_json

In method load_trim_component()

There is too much going on. You have pasted snippets I gave you for loading a json file into a hash, and saving a hash to a json file in the SAME method.

These were always meant to be in separate methods, and be called at different times depending upon the situation.

You need to separate “tasks” into their own methods with appropriate names so that your code reads better and even you can understand the logic.


In method load_trim_component() and perhaps elsewhere

The loading of dependency libraries only needs to happen once.
Traditionally, these require calls are made at the top of class or module definitions.

For example, in the import_custom_profile() method you use a conditional in modifier position to ensure that require 'fileutils' is only called once. (This is okay not being at the top of the module, but you might want to insert a comment up there that the FileUtils library is a dependency.)


In import_custom_profile() methodas @McGordon said, this …

    def import_custom_profile()
      custom_path = get_new_profile_path()
      if custom_path
        require 'filetutils' unless defined? FileUtils
      begin
        FileUtils.cp(custom_path, @profiles_path)
      rescue IOError => err
        UI.messagebox("Error copying profile to folder.\n#{err.message}")
      else
        puts "Profile Copy Cancelled"
        return
      end #if/else/if...
    end #import_custom_profile

needs to be like …

    def import_custom_profile()
      custom_path = get_new_profile_path()
      if custom_path
        require 'filetutils' unless defined? FileUtils
        begin
          FileUtils.cp(custom_path, @profiles_path)
        rescue IOError => err
          UI.messagebox("Error copying profile to folder.\n#{err.message}")
        end
      else
        puts "Profile Copy Cancelled"
      end #if/else/if...
    end #import_custom_profile

… Ie, you missed an end for the beginrescue block, and did not properly indent the block.
Also, the return statement was frivolous in this case, so I removed it.


BASIC THINGS TO KNOW

Once, you get a syntax error from a missing end, … all other errors are usually bogus. You need to fix the first error and reload to find the other errors.

Prefer String#<< over String#+ as the latter creates an extra String object for every two strings on each side of a +. The << append method modifies the receiver String. (Just beware of times when you should not modify the first String.)

1 Like

Thanks! I was unaware of that and the syntax error for missing ends. I’ll fix it! :grinning:

Yes, if you miss the closure of a block, the Ruby Interpreter will misread the rest of the file!

1 Like

I hate to bother you again but I’ve spent an hour searching and cannot find where the problem is. I fixed that closure…and my last end #CaydenWilson now is throwing an end of input error. I’m hoping a different set of eyes and your wealth of knowledge can find this as I somehow can’t. Thanks!

module CaydenWilson
  module ProTrim

    def prompt_user_selection
      selection = Sketchup.active_model.selection
      #if faces or groups are in selection
      #alert user to try again and close program
      if !only_edges_selected?(selection)
        UI.messagebox('Your selection cannot include faces or groups. Please select a trim path using edges only.' , MB_OK)
        return
      end #if

      paths = collect_paths_from_selection
      #if no selected path exists, alert user to retry and close program
      if !paths
        result = UI.messagebox('There is no selected path. Please select a trim path using edges only.' , MB_OK)
        return false if result = IDOK
      end #if
    end #prompt_user_selection

    def model_trim_command()
      trim = get_trim_choice(input)
      return unless trim #close if user cancels program
      trim_component_def = load_trim_component(trim)

      #If .skp file unretreiveable, alert user and close program
      if !trim_component_def
        result = UI.messagebox('File cannot be loaded. Please select a different trim profile.' , MB_OK)
        return false if result == IDOK
      else
        result = extrude_trim_profile(trim_component_def, paths)
        #Alert user modeling was successful and close program
        if result
          result = UI.messagebox('Trim created successfully.' , MB_OK)
          return false if result == IDOK
        end #if
      end #if/else
    end #model_trim_command

    def get_trim_choice()
      #Prompt user to choose trim profile from drop down menu
      prompts = ["Select your trim profile from the drop down menu: "]
      defaults = ["trim.skp"]
      trimlist = [trim_name_data]
      fields = [trimlist.join('|')]
      input = UI.inputbox(prompts, defaults, trimlist, fields, "Select Trim Profile")
      return unless input
      trim = input[0]
    end #get_trim_choice

    def load_trim_component(input)
      @profiles_path = File.join(__dir__, 'Profiles')
      #"Profiles" subfolder of this file's folder
      if Dir.exist?(@profiles_path)
        Dir.chdir(@profiles_path) {
          @trimfiles = Dir['*.skp']
        }
      else
        Dir.mkdir(@profiles_path)
      end #if/else
    end #load_trim_component

    def save_to_hash()
      #Creating a json file that has descriptions of
      #trim files reloaded into a hash
      require 'json' #dependant

      json = @trimhash.to_json
      #Write to disk
      filepath = File.join(@profiles_path, 'trim_profiles.json')
      begin
        File.write(filepath, json, mode: "w", encoding: "UTF-8")
      rescue =>
        UI.messagebox("There was an error with the file.
          Please choose a different trim profile.")
        puts err.inspect
      end #begin/rescue statement

      @trimhash = JSON.parse(json)
      @trimhash.keys
      @trimhash["trim_desc"]
    end #save_to_hash

    def get_trim_material_data()
      #Prompt user to choose trim profile from drop down menu
      prompts = ["Material Name:       ", "HEX Value: "]
      defaults = ["", ""]
      list = ["", ""]
      input = UI.inputbox(prompts, defaults, list, "Choose Trim Color")
      return unless input
      material_name = input[0]
      material_color = input[1]
    end #get_trim_material_data

    def create_trim_matreial(material_name, material_color)
      materials = Sketchup.active_model.materials[0]
      materials.each {|material| if material.display_name == material_name
        materialExists = true
      else
        materialExists = false
      end #check for material
      }

      #If material nonexistant create and add to pallet
      if materialExists == false
        #creating material
        material = materials.add material_name
        material.color = material_color
      end #if
    end #create_trim_material

    def extrude_trim_profile(trim_component_def, paths)
      @entities = Sketchup.active_model.active_entities
      #Explodes loaded component
      group = nil
      undo(mode, "Create New Trim")do
        group = entities.add_group #Added at orgin
        instance = group.entities.add_instance(trimdef, IDENTITY)
        group.transform!(transgroup)
        instance.explode
        face = group.entities.grep(Sketchup::Face).first
        face.followme(edges)
      end #undo
      return group

      #Preparing face for extrusion
      connected = face.all_connected
      face.material = trim
      face.back_material = trim
      #Extrudes trim profiles along path(s)
      face.followme(paths.grep(Sketchup::Edge))

      #Alerts user modeling is done
      result = UI.messagebox('Your trim profile was created successfully.' , MB_OK)
      return false if result == IDOK
    end #extrude_trim_profile

    def import_custom_profile()
      custom_path = get_new_profile_path()
      if custom_path
        require 'filetutils' unless defined? FileUtils
        begin
          FileUtils.cp(custom_path, @profiles_path)
        rescue IOError => err
          UI.messagebox("Error copying profile to folder.\n#{err.message}")
        end
      else
        puts "Profile Copy Cancelled"
        return
      end #if/else
    end #import_custom_profile

    def help()
      dialog = UI::HtmlDialog.new({
        :dialog_title => "ProTrim Help and Support",
        :scrollable => true,
        :resizable => false,
        :width => 1080,
        :height => 720,
        :left => 500,
        :top => 500,
        :style => UI::HtmlDialog::STYLE_UTILITY
        })
        dialog.set_url("https://caydenwilson017.wixsite.com/cwdesigns1")
        dialog.show
      end #dialog
    end #help

    #Checking if UI is loaded. If not, loads UI
    if !@loaded
      submenu = UI.menu("Extensions").add_submenu("ProTrim")

      tb = UI::Toolbar.new("ProTrim")

      cmd = UI::Command.new("Create Trim") {
        model_trim_command()
      }
      cmd.menu_text = "Create Trim"
      cmd.set_validation_proc {
        Sketchup.active_model.selection.empty? MF_GRAYED | MF_DISABLED : MF_ENABLED
      }
      UI.submenu("ProTrim").add_item(cmd)

      #Command that allows user to import their custom trim profiles
      cmd = UI::Command.new("Import Custom Profile") {
        import_custom_profile()
      }
      cmd.menu_text = "Import Custom Profile"
      UI.submenu("ProTrim").add_item(cmd)

      #Command directing user to support site
      cmd = UI::Command.new("Help") {
        help()
      }
      cmd.menu_text = "Help and Support"
      UI.submenu("ProTrim").add_item(cmd)
      @loaded = true
    end #@loaded
  end #ProTrim
end #CaydenWilson

Paste the code into the Ruby Console, and you get …

Error: #<SyntaxError: <main>:74: syntax error, unexpected '\n', expecting &. or :: or '[' or '.'
...ose a different trim profile.")
...                               ^

Which means the error is on line 74.

It WAS on line 81 … when @McGordon showed you what the error is, … in his post 4 hours ago !

Basically you cannot have literal Strings span multiple lines like you have there in your UI.messagebox argument list.

As he showed you … you can continue a statement with a backslash character, or insert an escaped n (which is the escape sequence for a newline, ie “\n”,) where you want the line break, and append another string to it. Ie …

        UI.messagebox("There was an error with the file.\n" <<
          "Please choose a different trim profile.")

However, your breaking the rules of indentation. Whenever you have argument lists spanning multiple lines, the closing parenthesis should be lined up with the start line, and each argument line should be indented 2 spaces (and ONLY 2 spaces.) Like so …

        UI.messagebox(
          "There was an error with the file.\n" <<
          "Please choose a different trim profile."
        )

The reason this done, is so that the block indentation lines show you where the block begins and ends.
This helps you not to forget the closing end, or closing ), or closing }, ], … etc.

image


There is also another error just above this on line 73. The error would be a RuntimeError because you wouldn’t see it until another error occurred and the rescue block gets called. Ie … you have …

      rescue =>

… without any reference to receive the exception object after the => operator.

As I showed you in the import_custom_profile() method, you should have an err (or whatever you like) reference to receive the exception object. You can choose a different reference name, but it must match what you use in the …

        puts err.inspect

… statement within the rescue block.


I’ve also noticed another issue. It is in method extrude_trim_profile().

Halfway down the method, you have a statement to return the group you’ve created.
The rest of the method will never be executed. (It looks like you’ve mixed 2 methods together and didn’t finish the edit.)

Also in this method, take note the do is a reserved word and should have spaces between it and any other code.

Your problems are twofold. Firstly, you making basic Ruby sytanx errors. I suggest going to the core Ruby doc entry page, and reread all the rdocs whose path start with "doc/syntax/"

Secondly, you are not paying close enough attention to what is told you (often several times.)
Programming is all about attention to details. If you cannot handle nurdy nitty details well, you will not be successful as a programmer.

This project you have chosen, is way beyond your skill level. I strongly suggest you shelve it for now, and start with smaller simpler extension projects, to gain skill and experience.

1 Like