How do I make Ruby Matrix initialize in 2024

Hi. I have been writing an extension using 2022. Some of my methods employ Matrix.columns and other Ruby Matrix methods. These have been working without issue and I have not used require ‘matrix.rb’ (Ruby version 2.7.2)

I just thought I’d better start using 2024 ( I didn’t like it because the mouse wheel orbit is very jumpy in this version for some reason…) Well now when I trial my extension I get an error that Matrix is uninitialized (dang!) … So I added the require ‘matrix.rb’, but to no effect. (Ruby version 3.2.2)
As well, now when I open the extension in 2022 I get:
C:/Program Files/SketchUp/SketchUp 2022/Tools/RubyStdLib/matrix.rb:654: warning: already initialized constant Matrix::SELECTORS
C:/Program Files/SketchUp/SketchUp 2022/Tools/RubyStdLib/Matrix.rb:654: warning: previous definition of SELECTORS was here

I have organized my app into files. The process side has 10, all in the same module called “Skew”.
every file has require ‘matrix.rb’.
The error I get is generated by Dan Rathburns nifty error tool and reads:

#<NameError: uninitialized constant Skew::Matrix>

it refers to:

def Skew.convert_sk_transformation_into_matrix(transformation)
ta = transformation.to_a
columns = [ta[0…3], ta[4…7], ta[8…11], ta[12…15]]
return Matrix.columns(columns)
end

I can’t think of what to try next.

The warnings about already initialized are because you are requiring matrix.rb multiple times. They are harmless, though annoying. You can avoid them by testing whether Matrix is defined before requiring the file or by centralizing such generic requires into one master file that requires your other files.

I’m not sure why Dan’s analyzer thinks there is a name Skew::Matrix. Would have to examine the code to try to figure it out. But perhaps the above suggestion would fix that too.

My nifty error tool?

Please refresh my memory or provide a link.

Agree with Steve. Usually an extension has a “loader” file which requires whatever libraries it needs and then requires all the other rb files of the extension. example …

# encoding: UTF-8

require 'matrix' unless defined?(Matrix)

module WalterJonathanVdk
  module Skew

    # Load the extension's files in proper order:
    ['main','commands','tool','gui'].each do |rb|
      Sketchup.require(File.join(__dir__, rb))
    end

  end
end

This was likely because another extension had already loaded Matrix before your extension loaded.

Firstly, you do not need (and should not use) the .rb filetype. Ruby’s require will load the file also looking for compiled libraries like so, dll, dylib, bundle, etc., as well as rb files. Since this is a library, you may never know what the filetype may be in future versions of Ruby. Best to leave it off. Ex …

require 'matrix' unless defined?(Matrix)

Secondly, I just tried this above command at the SU2024 console and it successfully loaded the Matrix class.

Why did mine work and yours did not? I have no idea.

FYI: Matrix is now one of the bundled Gems, so it actually gets loaded by the rubygems utility.

1 Like

Very grateful for all the informative feed back I am getting on this site. Thank you thank you.

def error_handler(error, methname, view)
        @error_handler = true
        line = error.backtrace_locations.first.lineno
        puts "Nifty Tool Error: #{error.class} in #{methname}, at #{line}"
        puts error.inspect
        view.model.abort_operation   
        reset_tool(view)
      end

Oh okay, I remember now. That was an example for use in other author’s tool code.

It was not itself meant to be called a “nifty error tool”.
It was the example that was the Nifty tool. That method was just a way of centrally handling errors.

So really you were supposed to change the prefix in the puts statement to match your class.

Ie … a more generic edition would be:

      def error_handler(error, methname, view)
        @error_handler = true
        line = error.backtrace_locations.first.lineno
        puts "#{self.name} Error: #{error.class} in #{methname}, at #{line}"
        puts error.inspect
        view.model.abort_operation   
        reset_tool(view)
      end

Something is amiss. There is also some confusion re the distinctions between Sketchup.require and Ruby’s require.

  1. Ruby’s require will never throw warning: already initialized constant unless the file being required exists in two locations.

  2. Ruby’s require is normally used with no path or a relative path. It can also be used with a full path. Sketchup.require is normally used with a full/absolute path.

  3. Ruby’s require uses RubyGems first, then $LOAD_PATH to look for a file. If the file is already loaded (checked via $LOADED_FEATURES), require will return false.

  4. Sketchup.require doesn’t resolve the file path via RubyGems, but it uses $LOAD_PATH and $LOADED_FEATURES.

Also, re code similar to:

require 'matrix' unless defined?(Matrix)

I would suggest always using a fully qualified constant (starts with ::), so the below would be better:

require 'matrix' unless defined?(::Matrix)

I think SketchUp API docs for Sketchup.require could be improved by comparing its behavior to Ruby’s require statement…

Also, in the past I have suggested that SketchUp add two methods:

Sketchup.require_relative
Sketchup.auto_load

EDIT: Updated based on feedback/correction from Dan

This true with regard to RubyGems, ie … for example you cannot load the Matrix library with:

Sketchup.require 'matrix'

… since it was moved from the Standard Library to the Bundled Gems. You must use the global require method which has been overridden by RubyGems.

However, the Sketchup.require method can use the LOAD_PATH array if the argument is a relative path.

SketchUp pushes the path to the current SketchUp version’s %AppData% "Plugins" directory path into the $LOAD_PATH array.

So, Sketchup.require (just like Kernel.require) will go through $LOAD_PATH appending relative path arguments to each array member in turn, testing for an existing file (in the filetype order given in the docs.) The doc for this method mentions it “will look for” the file given,

The proof is in the "Tools/extensions.rb" file and the documentation for the SketchupExtension class constructor. The doc instructs coders to use a relative path as the 2nd argument (with no filetype.)
The argument is assigned to the instance’s @path variable. Then in the internal load() instance method the relative @path variable is passed unaltered to Sketchup.require.

The class could have generated an absolute path (for the Sketchup.require method call) as it has already divined the extension registrar filepath and stored it in a @extension_path variable. It could have just appended the relative @path onto File.dirname(@extension_path) and passed that, thereby bypassing the unnecessary lookups through LOAD_PATH path folders.


The main difference between Kernel.require and Sketchup.require is that the latter will downcase the paths it pushes into the LOADED_FEATURES array.

But be aware that if the file is an unencrypted rb file, then Sketchup.require will just pass the path argument on to Kernel.require which does not downcase the file paths pushed into the LOADED_FEATURES array. So we often get a mix of downcased paths and camelcase paths.

Yes, within an extension’s files, there is no need to go looking for the correct path because the evaluation is already being done from the extension’s subfolder. So it is normal to do …

Sketchup.require(File.join(__dir__, "somefile"))

… where the global __dir__() method returns the absolute path to the file currently being evaluated.

It should speed up extension load times not having to search for the file to be loaded.

If this statement is in the top-level ObjectSpace what is the point?

I never suggest top level gems or libraries be required from inside an extension namespace.
And I cannot think of a good reason to load libraires from within a custom module or class.

Dan,

You’re correct, Sketchup.require does check using $LOAD_PATH if the path passed to it is not relative. I’ll update my above message.

I can’t recall if I checked it, and I always use Ruby’s require for Ruby files, and Sketchup.require for plugin/extension files.

I don’t recall looking for docs related to it, but plugin/extension authors may modify $LOAD_PATH, and may not be aware of filename collisions with native Ruby files. So, I use full paths. Goes along with why I’ve asked for Sketchup.require_relative

If one is lazy loading an extension, one may have require statements that are not top-level. In stand-alone Ruby world, requires are often not top-level when optional features require additional files.

I think you mean “not absolute” ?

I’ve never been against this. Mainly because we cannot use a refinement to “tweak” modules.

A mixin works …

# encoding: UTF-8
# require_relative.rb

module Mixin
  module RequireRelative

    def require_relative(filepath)
      caller_dir = ""
      stack = caller_locations(1, 1)
      if stack && stack.length > 0
        caller_dir = File.dirname(stack[0].path)
      end
      Sketchup.require(File.join(caller_dir, filepath))
    end

  end
end

Tested with a loader file and a bunch of dummy empty rb files …

# encoding: UTF-8
# SomeAuthor_SomeExtension/loader.rb

module SomeAuthor
  module SomeExtension

    require 'require_relative'
    extend ::Mixin::RequireRelative

    # Load a hash of localized strings for the GUI
    # from this extension's "lang" subdirectory:
    require_relative("lang/#{Sketchup.get_locale}")

    # Load the extension's files in proper order:
    ['main','commands','tool','gui'].each do |file|
      require_relative(file)
    end

    # ... more extension code ...

  end
end

Yes. I was going to say that, but couldn’t recall the distinction between absolute_path and realpath. So I changed to ‘relative’ and forgot the ‘not’…