Namespace in Multiple .rb Files

I am creating an extension and it is time to split everything out into multiple files.

I have in my main file…

require 'sketchup'

require_relative 'Model1'

module MyNameSpace

  module MyModels

    def self.create_model1

      ...

      model1

      ...

    end

    unless file_loaded?(__FILE__)

      # add menu items

      menu_Models = UI.menu('Extensions').add_submenu('My Models')

        menu_Model1.add_item('Model 1') {self.create_model1}
 
      ...

    end
  end
end

and in the second file

def model1

  ...

end

After reading about namespaces I included the namespace and module names in the second file, as I had seen in other extensions.

It was all working until a SketchUp restart and I got an
“Error: #<NameError: undefined local variable or method model1 for MyNameSpace::MyModels:Module>”

[code]
module MyNameSpace

module MyModels

def model1

  ...

end

end
end[/code]

After tearing my hair out I removed the namespace and module references and after a SU restart it works again.

What have I misunderstood about namespaces and module names? If it means anything I am not using classes at all.

Also, is there a way to reset the Ruby interpreter so a SU restart is not required?

Hi,
I share on github my template for sketchup extension

At the top of your extension
create a module with your name as author
create a module with the name of your extension

module Adebeo
module ExtensionName
ISDEVELLOPPEMENT = true
end
end

inside another file create a class inside this 2 namespaces:

module Adebeo::ExtensionName
	class Controler
		attr_accessor
		def initialize()
			@model = Sketchup.active_model
			@definitions = @model.definitions
		end
	end
end

Please find a full exemple, here:

The Discourse forum engine does not use bbCode. See:

… becuase you ae defining the method in the global ObjectSpace, which is class Object. This means your method definition will get added to everything as everything in Ruby is an object of some class that inherits from Object.
And, of course it seems to work, as global methods are just that, accessible from every scope because they are insinuated into everything.

You have not yet understood the difference between instance methods and module methods.

In your first file, you define a module method that requires qualification (self.method_name or from outside … MyNameSpace::MyModels.method_name.)

In your second file, you define an instance method without qualification.

If you would like to call any method from within your submodule (in any of it’s files,) without qualification, then in the first file, at the top of the submodule, add a statement to mix the submodule into itself …

module MyNameSpace
  module MyModels

    extend self

    # Define instance methods ...

See: Object#extend

… and chapters on mixin modules in any good Ruby book.


Thanks Denis,

If I use

MyName::ExtName

I get uninitialized constant MyName

This is because the outer constant MyName is not yet defined and you are trying to define (or open for edit) the inner module ExtName.

There are 2 workarounds …

1. Use nested definitions for the first file:

module MyName
  module Extname

2. Just before the nested definition, for the first file, insert a blank module block:

module MyName; end
module MyName::Extname
  # ...

Hi Dan,

workaround 1 is how I initially had them (nested)

Uh…huh, so use (2) if you want to gain an indent.

Keep in mind that your first file, will actually be the extension registrar script that goes in the “Plugins” folder. All other files go in the extension’s subfolder. (The subfolder and registrar script must have the same name.)

Example registrar file:

module CoyoteToolworks
  module CrazyWidget

    EXTENSION ||= SketchupExtension.new('','main')
    EXTENSION.instance_eval do
      self.name        = 'Crazy Widget Tool'
      self.version     = '1.3.0'
      self.creator     = 'Coyote Toolworks, Ltd.'
      self.copyright   = '©2020, by author.'
      self.description = 'Draws widgets faster than a roadrunner.'
      Sketchup.register_extension(self,true)
    end

  end
end

At the moment I am just calling load in the console on the first file during development.

The file I am calling first is not the file I would put in the plugins folder. It is the file that creates menu items and will call functions defined in other files as a result of menu choices.

1 Like

I am guessing I have some stupid error I can’t see…

Your extension files can have whatever name you wish. (Many devs use names like “menu.rb” and “gui.rb” for the file that defines UX commands and such.)

You can also name the “loader file” (ie, the 2nd argument to the SketchupExtension constructor,) whatever you wish. (I myself use “loader”, many devs use “main”. :shrug:)

What error ?

If only I knew …

my menu file

require 'sketchup'
# require 'extensions'

# require 'Icosahedron'
require_relative 'Icosahedron'
#require_relative 'Test'

module BHL

  module Polyhedrons

    def self.create_icosahedron

      if not @frequency

        @frequency = 1

      end

      prompts = ["Frequency"]

      values = [@frequency]

      list = ["1|2|3|4|5|6", @frequency]

      results = UI.inputbox(prompts, values, list, "Frequency")

      return if not results

      @frequency = results[0]

      if @frequency == 1

        icosahedron_CIV1

      elsif @frequency == 2

        icosahedron_CIV2

      elsif @frequency == 3

        icosahedron_CIV3

      end

    end # self.create_icosahedron

    def self.octahedron

      UI.messagebox "Octa coming later"

    end # self.create_octahedron

    unless file_loaded?(__FILE__)

      menu_Poly = UI.menu('Extensions').add_submenu('Polyhedrons')

        menu_Icosa = menu_Poly.add_submenu('Icosahedron')

          menu_Icosa.add_item('Icosa Class I') {self.create_icosahedron}

          menu_Poly.add_item('Octahedron') {self.octahedron}

#        menu_Poly.add_separator

 #       menu_Poly = UI.menu('Extensions').add_submenu('Test')

  #        menu_Poly.add_item('Test Output Box') {self.test_output}

      file_loaded(__FILE__)

    end # unless file_loaded?(__FILE__)

  end # module Polyhedrons

end # module BHL

worker file

module BHL

  module Polyhedrons

    def get_icosa_vtxs(radius)

      #  phi = (Math::sqrt(5) + 1) / 2
      #  atanPhi = Math::atan(Phi)

      pi = Math::PI

      atan_2 = Math::atan(2)

      vtx_X_1 = radius * Math::sin(atan_2) * Math::sin(0.4 * pi)       # e
      vtx_X_2 = radius * Math::sin(atan_2 / 2)                         # s
      vtx_Y_1 = radius * Math::sin(atan_2)                             # b
      vtx_Y_2 = radius * Math::sin(atan_2) * Math::cos(0.4 * pi)       # f
      vtx_Y_3 = radius * Math::sin(atan_2) * Math::sin(0.3 * pi)       # c
      vtx_Z_1 = radius * Math::sin(atan_2) / (2 * Math::sin(0.3 * pi)) # d

      arr_Vtxs = Array.new(12)

      arr_Vtxs[ 0] = Geom::Point3d.new(         0.0,         0.0,        radius.m) #  0,  0,  r
      arr_Vtxs[ 1] = Geom::Point3d.new(         0.0, - vtx_Y_1.m, (1 - vtx_Z_1).m) #  0, -b,  r - d
      arr_Vtxs[ 2] = Geom::Point3d.new(   vtx_X_1.m, - vtx_Y_2.m, (1 - vtx_Z_1).m) #  e, -f,  r - d

      arr_Vtxs[ 3] = Geom::Point3d.new(   vtx_X_2.m,   vtx_Y_3.m, (1 - vtx_Z_1).m) #  s,  c,  r - d
      arr_Vtxs[ 4] = Geom::Point3d.new( - vtx_X_2.m,   vtx_Y_3.m, (1 - vtx_Z_1).m) # -s,  c,  r - d
      arr_Vtxs[ 5] = Geom::Point3d.new( - vtx_X_1.m, - vtx_Y_2.m, (1 - vtx_Z_1).m) # -e, -f,  r - d

      arr_Vtxs[ 6] = Geom::Point3d.new(   vtx_X_2.m, - vtx_Y_3.m, (vtx_Z_1 - 1).m) #  s,  c,  d - r
      arr_Vtxs[ 7] = Geom::Point3d.new(   vtx_X_1.m,   vtx_Y_2.m, (vtx_Z_1 - 1).m) #  e,  f,  d - r
      arr_Vtxs[ 8] = Geom::Point3d.new(         0.0,   vtx_Y_1.m, (vtx_Z_1 - 1).m) #  0,  b,  d - r

      arr_Vtxs[ 9] = Geom::Point3d.new( - vtx_X_1.m,   vtx_Y_2.m, (vtx_Z_1 - 1).m) # -e,  f,  d - r
      arr_Vtxs[10] = Geom::Point3d.new( - vtx_X_2.m, - vtx_Y_3.m, (vtx_Z_1 - 1).m) # -s, -c,  d - r
      arr_Vtxs[11] = Geom::Point3d.new(         0.0,         0.0,      - radius.m) #  0,  0, -r

      arr_Vtxs

    end # get_icosa_vtxs(radius)

    def icosahedron_CIV1

      # This method creates an icosahedron within a component in the model.

      arr_Face_Map = [[0, 1,  2], [0, 2,  3], [0, 3,  4], [0,  4,  5], [0,  1,  5], 
                      [1, 2,  6], [2, 6,  7], [2, 3,  7], [3,  7,  8], [3,  4,  8], 
                      [4, 8,  9], [4, 5,  9], [5, 9, 10], [1,  5, 10], [1,  6, 10], 
                      [6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [6, 10, 11]]

      radius = 1.0

      arr_Icosa_Vtxs = get_icosa_vtxs(radius)

      Sketchup.file_new

      model = Sketchup.active_model

      model.start_operation('Create Icosahedron', true)

      component = model.definitions.add

      component.name = "Icosahedron"

      entities = component.entities

      # add faces

      arr_Face_Map.each do |face|

        component.entities.add_face(arr_Icosa_Vtxs[face[0]], arr_Icosa_Vtxs[face[1]], arr_Icosa_Vtxs[face[2]])

      end

      transform = Geom::Transformation.new([0, 0, 0]) # an empty, default transformation.

      model.active_entities.add_instance(component, transform) 

      model.commit_operation

    end # icosahedron_CIV1

    def icosahedron_CIV2

      UI.messagebox "Icosa V2 + coming later"

    end # icosahedron_CIV2

    def icosahedron_CIV3

      UI.messagebox "Icosa V3 + coming later"

    end # icosahedron_CIV3

  end # module Polyhedrons

end # module BHL

I solved your error (unqualified method calls) for you above …

… also, the ol’ “Pick Axe” Ruby book suggests using blank () at the end of method calls when there are no arguments. (It helps the interpreter to immediately know that this is a method call and not a local variable reference. So the interpreter does not have to do a search though the scope to determine what it is, … a method reference or a variable reference.)
Ie …

if @frequency == 1

        icosahedron_CIV1()

A tidbit, … Ruby has modifier position conditionals …

@frequency = 1 if not @frequency

Ruby also has conditional assignment …

@frequency ||= 1

… (but keep in mind you cannot use this for boolean references.)

Thanks Dan,

extend self has fixed it.

I tried method 2 but didn’t successfully qualify the call. I tried MyName::Extname::icosahedron_CIV1 and
MyName::Extname.icosahedron_CIV1

I appreciate the tidbits. The conditional assignment is interesting.

There is a lot to learn with Ruby. A lot of different ways to do things and quite some difference to what I am used to. I get brain strain.