Import CSV or Excel Data into SketchUp

  1. Ruby indents are 2 spaces not 4.

  2. I had given you the correct way to conditionally add one menu item at the end of all of the extension code, using the value of the module var @@loaded. You moved that code to an improper place without a conditional if keeping the menu item from creating multiple entries in the menu should you need to reload the file again during development.

  3. I had correctly put the control of the import command in a method rather than define it within a code block passed directly to the menu item constructor call. Doing it your way will mean you have to close SketchUp and restart it each time you make code changes and want them to be applied. This slows down development.
    Ruby is a dynamic language. This means that classes, modules and methods can be redefined at anytime. So if you tweak a method and want to apply the changes, then you reload the rb file you just edited and saved using Ruby’s global load() method instead of the global require() method.

  4. The require 'csv' statement in the parse_csv_data() method is not needed because it is loaded inside a rescue block in the get_csv_data() method, which should always be called first as it only calls the load_csv_data() and parse_csv_data() data methods IF there is no error in loading the CSV library.

  5. The require 'win32ole' statement in the parse_xls_data() method is not needed because it is loaded inside a rescue block in the get_xls_data() method, which should always be called first as it only calls the load_xls_data() and parse_xls_data() data methods IF there is no error in loading the WIN32OLE library.

  6. Calling the == method on a boolean object within a boolean evaluation is frivolous.
    ie … if cbl == false
    The reference cbl will be evaluated anyway, and as an expression of the if statement the result will be tested for truthiness / falsity anyway. So use …
    unless cbl … or … if !cbl

  7. On line 100 you have the reference identifier chosen just sttting there on a line by itself with no assignment or anything else. It was not previously defined and nothing is being done with it. I remove it.

  8. Your colorByLayer() method has several problems.
    a. It should not be a module method. (You do not need to define it with self.)
    b. Ruby methods are named with all lowercase words separated with underscores. CamelCase identifiers are reserved for Module and Class identifiers. Ie color_by_layer_toggle()
    c. It is not called from anywhere in the file, so I’ll omit this from the code for simplicity sake.

  9. In several places you inserted a space between a method identifier and it’s parameter list opening parenthesis. This is a big “no no” and generates a warning. In future Ruby releases this warning is going to change to a SyntaxError and such files will no longer load.

A lot of what I am seeing is you not knowing the basics of Ruby programming.
Really you need to make your life (and ours) easier by taking advantage of all the free tutorials and books on Ruby that are available.


Now the biggest problem that you yourself caused and why you get the NoMethodError is because when YOU did what I explain in (3) above, YOU also moved all the method definitions into a local block of code that is passed to the add_item() method when you created your menu item.
(Code blocks are a scope level.)

I will repost the correct way with a few extra statements of yours added. BUT this is the last time.
If you are going to mess up the code by moving things around then I’ll no longer waste my time.


@sWilliams, My original template above was written correctly. The OP messed it up. Yours has non-rubyish method names and incorrect placement of module variable definitions. Module variables MUST be defined before any use. They are NOT usually put at the bottom of code. They and constants are usually put at the top of the code.
I know you are trying to help, but giving the newb two competing templates and advice is counterproductive, IMO.


Here is the updated template …

Sirius_LoudspekerImporter_0.2.0.rb (3.8 KB)

Which looks like this …

# encoding: UTF-8

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

# Show ruby console, to see if there are any mistakes
SKETCHUP_CONSOLE.show

module Sirius
  module LoudspekerImporter
  
    extend self

    # CONSTANTS
    
    VERSION = '0.2.0'

    IS_WIN =( Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i )

    if IS_WIN

      require 'win32ole'

      wsh_shell = WIN32OLE.new("WScript.Shell")

      USER_DESKTOP_PATH ||= wsh_shell.SpecialFolders("Desktop")

      USER_DOCUMENTS_PATH ||= wsh_shell.SpecialFolders("MyDocuments")

      wsh_shell = nil # let GC clean it up

    else # it's a Mac

      USER_DESKTOP_PATH ||= "~/Desktop"

      USER_DOCUMENTS_PATH ||= "~/Documents"

    end

    # MODULE VARIABLES
    @@loaded = false unless defined?(@@loaded)
    @@last_path ||= USER_DESKTOP_PATH

    # PLUGIN METHODS
   
    def import_control
      UI.messagebox("Let's import some loudspeakers !")
      filepath = UI.openpanel(
        "Import loudspeaker file...",
        @@last_path,
        "CSV (Comma Separated values)|*.csv|XLS (Excel Workbook)|*.xls;*.xlsx||"
      )
      if filepath.nil? # UI.openpaneel returns nil if user cances the dialog
        UI.messagebox("Import cancelled by user.")
        return false
      end
      case File.extname(filepath)
      when '.csv'
        @@last_path = File.dirname(filepath)
        get_csv_data(filepath)
      when '.xls','.xlsx'
        @@last_path = File.dirname(filepath)
        get_excel_data(filepath)
      else
        msg = "Unknown filetype! Import cancelled."
        puts "#{Module.nesting[0].name}: #{msg}"
        puts "  Filepath: \"#{filepath}\""
        UI.messagebox(msg)
      end
    end ###

    def get_csv_data(filepath)
      begin
        require 'csv' # load CSV library on demand
      rescue => e
        msg = "Error loading CSV Library! Import cancelled."
        puts "#{Module.nesting[0].name}: #{msg}"
        UI.messagebox(msg)
      else
        # No error. Load the CSV file and parse the data.
        puts "#{Module.nesting[0].name}: Loading loudspeaker data file ..."
        puts "  \"#{filepath}\""
        data = load_csv_data(filepath)
        parse_csv_data(data)
      end
    end ###

    def get_excel_data(filepath)
      begin
        require 'win32ole' # load WIN32OLE class on demand
      rescue => e
        msg = "Error loading WIN32OLE class! Import cancelled."
        puts "#{Module.nesting[0].name}: #{msg}"
        UI.messagebox(msg)
      else
        # No error. Load the XLS file and parse the data.
        puts "#{Module.nesting[0].name}: Loading loudspeaker data file ..."
        puts "  \"#{filepath}\""
        data = load_xls_data(filepath)
        parse_xls_data(data)
      end
    end ###

    def load_csv_data(filepath)
      data = []
      # File load code goes here
      CSV.read(filepath, headers: true) do |row|
        data << row
      end
    rescue => e
      puts e.inspect
      UI.messagebox("Error reading CSV file.\n#{e.message}")
    else
      UI.messagebox("#{data.length}, Loudpspeakers Read from File.")
    ensure
      return data # may be empty if a CSV error occurs
    end ###

    def load_xls_data(filepath)
      data = []
      # File load code goes here 
      UI.messagebox("#{data.length}, Loudpspeakers Read from File.")
      return data
    end ###

    def parse_csv_data(data)
      # Parse code goes here
      #UI.messagebox("#{data.length}, Loudpspeakers Imported..")
    end ###

    def parse_xls_data(data)
      # Parse code goes here
    end ###


    # Define Menu items only ONCE
    unless @@loaded
      UI.menu("Extensions").add_item("LS Importer") { import_control() }      
      @@loaded = true
    end

  end
end

:bulb:

1 Like