Got problem with add.menu tool(beginner)

My ruby works okay

But when I add toolbar system, It doesn’t work

I can’t figure out where is wrong

require 'sketchup.rb'

class MakeboxTool

  model = Sketchup.active_model
  entities = model.active_entities
  selection = model.selection
  faces = selection.grep(Sketchup::Face)
  faces.each{|face|
    group = entities.add_group(face)
    group2 = group.copy
    group.explode
    entities2 = group2.entities
    before = entities2.grep(Sketchup::Face)
    before[0].material = [50, 255, 255]
    before[0].pushpull( 10.mm )
    new_faces = entities2.grep(Sketchup::Face) - before
    upper_face = new_faces.find {|f| f.normal == before[0].normal.reverse }

  unless upper_face.nil?
    start_point = upper_face.bounds.center
    offset_vector = upper_face.normal
    offset_vector.length = 90.mm
    end_point = start_point.offset(offset_vector)
  end
    edge = group2.entities.add_line(start_point, end_point)
    layer = Sketchup.active_model.layers.add("win")
    group2.layer = layer
    entities2.each{|e| e.layer = layer }

  if !model.active_path.nil?
    instance = model.active_path.last
    definition = instance.definition
    group2_copy = group2.parent.parent.parent.entities.add_instance(group2.definition,group2.transformation)
    group2.erase!
  end
  
    }


end # of makebox class

if( not file_loaded?("makebox.rb") )
	UI.menu("Plugins").add_item("Start makebox Tool") { Sketchup.active_model.select_tool MakeboxTool.new }
	dir = Sketchup.find_support_file("Plugins")
	cmd = UI::Command.new("Start makebox Tool") { Sketchup.active_model.select_tool MakeboxTool.new }
	cmd.large_icon = cmd.small_icon = dir+"/makebox.png"
	cmd.status_bar_text = cmd.tooltip = "Tool for making blue boxes"
	tb = UI::Toolbar.new("makebox")
	tb.add_item cmd
	tb.show if tb.get_last_state == -1
end

file_loaded("makebox.rb")

There are two major things wrong with your code.

First, your MakeboxTool class has only loose class-scope code, no methods (def’s) at all, not even initialize. So, the code is run as the file loads, but thereafter is dead. Creating an instance via MakeboxTool.new does not cause the class-scope code to run again, so your menu item and toolbar button just create dead objects.

You could convert the class into a one-shot by wrapping the loose code in def initialize…end. initialize is the method run to initialize an object created by new.

But, that takes us into the second major issue: your code does not implement any of the methods of the Tool protocol. It does not really qualify as a thing that should be activated using select_tool. If you want a one-shot, you could create a class method (def self.methodname) and invoke it in the command’s code block instead of selecting a Tool.

Edit: beyond those obvious issues, I did not analyze your code to see if it does what you expected. Also, it should be wrapped within a uniquely-named module to isolate its stuff from any possible conflict with other code.

2 Likes

Agree with Steve.

(1) The only class definitions at the toplevel ObjectSpace should be Ruby core classes (and perhaps the occasional global API class.)

(2) Seek to leave primitive geometry assigned to use “Untagged” (aka “Layer0”.)
Assigned only the group or component instance contexts to use other layers / tags.

(3) Your indentation is abnormal. (Ruby uses 2 space indentation.)
Please do not outdent! It breaks indent alignment lines in code editors.

1 Like
require 'sketchup.rb'

module Makebox

 #extend self
  
  def self.makebox
    model = Sketchup.active_model
    entities = model.active_entities
    selection = model.selection
    faces = selection.grep(Sketchup::Face)
    faces.each{|face|
      group = entities.add_group(face)
      group2 = group.copy
      group.explode
      entities2 = group2.entities
      before = entities2.grep(Sketchup::Face)
      before[0].material = [50, 255, 255]
      before[0].pushpull( 10.mm )
      new_faces = entities2.grep(Sketchup::Face) - before
      upper_face = new_faces.find {|f| f.normal == before[0].normal.reverse }
      unless upper_face.nil?
        start_point = upper_face.bounds.center
        offset_vector = upper_face.normal
        offset_vector.length = 90.mm
        end_point = start_point.offset(offset_vector)
      end
      edge = group2.entities.add_line(start_point, end_point)
      layer = Sketchup.active_model.layers.add("win")
      group2.layer = layer
      entities2.each{|e| e.layer = layer }

      if !model.active_path.nil?
        instance = model.active_path.last
        definition = instance.definition
        group2_copy = group2.parent.parent.parent.entities.add_instance(group2.definition,group2.transformation)
        group2.erase!
      end

      }


  end
end

if( not file_loaded?("makebox.rb") )
    UI.menu("Plugins").add_item("Start makebox Tool") { self.makebox }
    dir = Sketchup.find_support_file("Plugins")
    cmd = UI::Command.new("Start makebox Tool") { self.makebox }
    cmd.large_icon = cmd.small_icon = dir+"/makebox.png"
    cmd.status_bar_text = cmd.tooltip = "Tool for making blue boxes"
    tb = UI::Toolbar.new("makebox")
    tb.add_item cmd
    tb.show if tb.get_last_state == -1
end

file_loaded("makebox.rb")

I created a def self.methodname

But still same

You close the Makebox module with an end statement before the code that creates the menu and toolbar. As a result, the method makebox is no longer in the namespace when you wire it into the menu and command. You need to use Makebox.makebox, not self.makebox. Then it works.

Edit: let me elaborate for your edification.

In Ruby there is at all times an active “receiver” object. It is referenced by the reserved name “self”. Method calls that are not qualified with a specific object (e.g. do_it() vs foo.do_it()) are automatically dispatched to self, as are ones that are explicitly qualified (e.g. self.makebox). The possibly confusing thing is that when you enter a module or class block, that module or class object is made the receiver. The syntax “def self.makebox” vs “def makebox” distinguishes between a method that belongs to the current module’s own namespace vs a method that will be added to another module or class namespace when this module is mixed in.

When you end the module Makebox, “self” changes back to whatever object was active before the module statement. In this case it is the global “main” object. The method def’d in module Makebox is not in main’s namespace, so the calls in the menu and command fail to find it. Putting the explicit qualifier Makebox.makebox tells Ruby to look for the method in Makebox’s namespace.

2 Likes

I disagree. Agent needs to put ALL his code within his modules.

Also the Makebox module should be a submodule inside Agent’s toplevel author module.

Each of Agent’s extensions need to be within an extension subfolder, not in the "Plugins" folder.
Then Agent can use the global __dir__() method, instead of Sketchup.find_support_file("Plugins")
… and File.join(__dir__,'makebox.png') instead of dir+"/makebox.png"

2 Likes

You are right. While obviously knowing the fact that the offending code was “loose at top level” I just addressed the “why didn’t it work” question. And a better answer would be to put all the code within the module.

1 Like