Creating button using sketchup Ruby

Hi everyone
I’m trying to make a simplified version of sketchup using customed buttons (icons).
Unfortunately I’m a total newbie using Ruby language.
Here’s the goal.
Create four (4) buttons which allow the user to move an object using the keyboard arrows.
I know how to make a toolbar, with customed buttons, but I’m unable to assign an action/command to this button.
Here’s the code that I found on the internet for the toolbar:

#add menu
UI.menu(“Extensions”).add_item(“test menu”){

  • UI.messagebox(“you just clicked test menu”)*
  • }*

toolbar = UI::Toolbar.new “test”
cmd1 = UI::Command.new(“Bouton 1”) {

  • UI.messagebox(“Bouton 1 cliqué”)*
  • }*
  • icon1 = File.join(dir, ‘images’,‘FredoCorner_round.svg’)*
  • cmd1.small_icon = icon1*
  • cmd1.large_icon = icon1*
  • cmd1.tooltip = “Round Corner”*
  • cmd1.status_bar_text = “Pour arrondir les arêtes”*
  • toolbar = toolbar.add_item cmd1*
  • toolbar.show*

What I’d like to do is to assign is that kind of lines;

model = Sketchup.active_model
sel = model.selection[0]
tr = Geom::Transformation.translation([50,0,0])

sel.transform!(tr)

But I totaly don’t know how to write the code properly to move the object using the button.
The final purpose is to be able to use a Surface Hub for editing simple sketchup models.

Thanks.

At first, your code above is hardly readable, so I would like to ask you to properly post the code:
[How to] Post correctly formatted and colorized code on the forum? - Developers - SketchUp Community



Secondly: You can start to learn starting from e.g. here:
Welcome! | SketchUp Developer
…and many other resources collected by Dan:
Ruby Learning Resources [WikiLists] - Developers / Ruby API - SketchUp Community



Now, about your question very briefly. Be aware that you need to properly namespace your code if you want to publish it… more you can read the links above… :wink:

The " action/command" you are looking for called method, which you have to define:

def my_first_method()
  model = Sketchup.active_model
  sel = model.selection[0]
  tr = Geom::Transformation.translation([50,0,0])

  sel.transform!(tr)
end

Then you can create a command object to call this method.

.
.
  cmd1 = UI::Command.new(" Bouton 1") {
    my_first_method()
  }
.
.
2 Likes

Hi,
I’m really thankful to you for your message.
Excuse me for the way I posted the topic, as I said I’m a total newbie.
Thanks to your advices I’ve been able to write this (quite) correctly. I’m sure it’s not perfect but I’ll do my best.
Thank you.

1 Like

You can edit your post using the pencil icon to correct the code snippet.

2 Likes

Hi everyone.
Using the tips from Dezmo I’ve managed to edit the code a little bit more properly.
Now I Have two other issues:

First one:
I want to incorporate another function allowing to rotate the selected entity on Z axe.
The sub submenu works correctly, but the button doesn’t.
I’ve tried several things, but as I said, newby working here.
I want te Rotate button to execute the function Def rotate90
Don’t know if you can help me with this one.
I’ve attached some image files.

Second one;
I’ve inserted blank buttons but I think there is a better way to do it, because I’ve edited a grey image to stay in place of the button, and the roll over are still there because I stupidly copied the linecode from another one. But I’m sure there is a way to make it simply.

Here are the screenshots:



And the code:

require “sketchup.rb”

#add menu
UI.menu("Extensions").add_item("test menu"){
	UI.messagebox("you just clicked test menu")
	}
	
toolbar = UI::Toolbar.new "test"

def deplacement_x_gauche()
  model = Sketchup.active_model
  sel = model.selection[0]
  tr = Geom::Transformation.translation([-135,0,0])

  sel.transform!(tr)
end

def deplacement_y_haut()
  model = Sketchup.active_model
  sel = model.selection[0]
  tr = Geom::Transformation.translation([0,135,0])

  sel.transform!(tr)
end

def deplacement_x_droite()
  model = Sketchup.active_model
  sel = model.selection[0]
  tr = Geom::Transformation.translation([135,0,0])

  sel.transform!(tr)
end

def deplacement_y_bas()
  model = Sketchup.active_model
  sel = model.selection[0]
  tr = Geom::Transformation.translation([0,-135,0])

  sel.transform!(tr)
end

def selected_comps_and_groups
    mm = Sketchup.active_model
    ss = mm.selection
	return nil if ss.empty?
	ss.each do |cc| 
		return nil if not ((cc.instance_of? Sketchup::ComponentInstance) or (cc.instance_of? Sketchup::Group))
	end	
    ss
end

def rotate90(sel, axis)
	rv = Geom::Vector3d.new(0,0,1) if axis == "z"
	ra = -90.degrees
	#rt = Geom::Transformation.rotation(rp, rv, ra) 
	sel.each do |ent|
		rp = Geom::Point3d.new(ent.bounds.center) 		#rotation point
		ent.transform!(Geom::Transformation.rotation(rp, rv, ra))
	end	
end

if( not file_loaded?("collierstoolbar.rb") )
    UI.add_context_menu_handler do |menu|
		if menu == nil then 
			UI.messagebox("Error setting context menu handler")
		else
			if (sel = selected_comps_and_groups)
				sbm = menu.add_submenu("Rotate 90")				
				sbm.add_item("Around Blue") {rotate90 sel, "z"}
			end
		end
        
	end
end

cmd1 = UI::Command.new("Vide") {}
	icon1 = File.join(__dir__, 'collierstoolbar', 'images','vide.png')
	cmd1.small_icon = icon1
	cmd1.large_icon = icon1
	cmd1.tooltip = "Gauche"
	cmd1.status_bar_text = "Pour déplacer d'une trame vers la gauche"
	toolbar = toolbar.add_item cmd1
	toolbar.show
	
cmd2 = UI::Command.new("Haut") {
	deplacement_y_haut()
	}	
	icon2 = File.join(__dir__, 'collierstoolbar', 'images','UP.png')
	cmd2.small_icon = icon2
	cmd2.large_icon = icon2
	cmd2.tooltip = "Haut"
	cmd2.status_bar_text = "Pour déplacer d'une trame vers le haut"
	toolbar = toolbar.add_item cmd2
	toolbar.show

cmd3 = UI::Command.new("Vide") {}
	icon3 = File.join(__dir__, 'collierstoolbar', 'images','vide.png')
	cmd3.small_icon = icon3
	cmd3.large_icon = icon3
	cmd3.tooltip = "Gauche"
	cmd3.status_bar_text = "Pour déplacer d'une trame vers la gauche"
	toolbar = toolbar.add_item cmd3
	toolbar.show	
	
cmd4 = UI::Command.new("Gauche") { 
	deplacement_x_gauche()
	}
	icon4 = File.join(__dir__, 'collierstoolbar', 'images','LEFT.png')
	cmd4.small_icon = icon4
	cmd4.large_icon = icon4
	cmd4.tooltip = "Gauche"
	cmd4.status_bar_text = "Pour déplacer d'une trame vers la gauche"
	toolbar = toolbar.add_item cmd4
	toolbar.show

cmd5 = UI::Command.new("Rotation") {
	rotate90(sel, "z")
	}	
	icon5 = File.join(__dir__, 'collierstoolbar', 'images','ROTATION.png')
	cmd5.small_icon = icon5
	cmd5.large_icon = icon5
	cmd5.tooltip = "Rotation"
	cmd5.status_bar_text = "Pour tourner de 90°"
	toolbar = toolbar.add_item cmd5
	toolbar.show

	
cmd6 = UI::Command.new("Droite") {
	deplacement_x_droite()
	}	
	icon6 = File.join(__dir__, 'collierstoolbar', 'images','RIGHT.png')
	cmd6.small_icon = icon6
	cmd6.large_icon = icon6
	cmd6.tooltip = "Droite"
	cmd6.status_bar_text = "Pour déplacer d'une trame vers la droite"
	toolbar = toolbar.add_item cmd6
	toolbar.show
	
cmd7 = UI::Command.new("Vide") {}
	icon7 = File.join(__dir__, 'collierstoolbar', 'images','vide.png')
	cmd7.small_icon = icon7
	cmd7.large_icon = icon7
	cmd7.tooltip = "Gauche"
	cmd7.status_bar_text = "Pour déplacer d'une trame vers la gauche"
	toolbar = toolbar.add_item cmd7
	toolbar.show
	
cmd8 = UI::Command.new("Bas") {
	deplacement_y_bas()
	}	
	icon8 = File.join(__dir__, 'collierstoolbar', 'images','DOWN.png')
	cmd8.small_icon = icon8
	cmd8.large_icon = icon8
	cmd8.tooltip = "Bas"
	cmd8.status_bar_text = "Pour déplacer d'une trame vers le bas"
	toolbar = toolbar.add_item cmd8
	toolbar.show
	
cmd9 = UI::Command.new("Vide") {}
	icon9 = File.join(__dir__, 'collierstoolbar', 'images','vide.png')
	cmd9.small_icon = icon9
	cmd9.large_icon = icon9
	cmd9.tooltip = "Gauche"
	cmd9.status_bar_text = "Pour déplacer d'une trame vers la gauche"
	toolbar = toolbar.add_item cmd9
	toolbar.show


	
	def function_call()
		UI.messagebox("Function called from Toolbutton")
	end

Because in a submenu (actually it is a context menu creation procedure) the sel variable is defined and passed it to rotate90 method. You should do similar when you are creating the cmd5.
Actually the

if (sel = selected_comps_and_groups)
..

Is not so “elegant”
better to

sel = selected_comps_and_groups
if sel
..

__
All “menu and toolbar things” for code you don’t want to reload need to be inside #file_loaded? in combination with #file_loaded load guards . This will protect your UI setup from creating duplicate menus and toolbars.

__
You do not need to use toolbar.show several times, it is enough to show after all item added to the toolbar.
Also you do not need to reassign the Toolbar object to toolbar variable e.g.:
toolbar = toolbar.add_item cmd9
__

The snippets are still not in a proper namespace… (that is okay for testing yourself, but it is really important to know to do it later.)


This is okay, I think. You may assign empty string e.g.

 cmd9.tooltip = ""
 cmd9.status_bar_text = ""

then use the #set_validation_proc method to change whether the command is enabled. Something like this

cmd9.set_validation_proc { MF_GRAYED }
1 Like

Some basic Ruby issues:


Unlike JavaScript, Ruby does not enforce parenthesis around conditional expressions.
Ie:

if( not file_loaded?("collierstoolbar.rb") )

You can use parenthesis to make complex expressions easier to understand or to force a certain order of evaluation.

But generally simple conditional expressions should omit the parenthesis:

if not file_loaded?("collierstoolbar.rb")

It is always best to use parenthesis around instance method call argument lists.

Only global methods from Kernel, BasicObject, Object, Module and Class, (those that are called without a receiver object and dot notation,) can be called without parenthesis. These kinds of methods are known as having “keyword status” (or “reserved word status”) as if they were interpreter functions.

sbm.add_item("Around Blue") {rotate90 sel, "z"}

… should be:

sbm.add_item("Around Blue") { rotate90(sel, "z") }

Another example:

toolbar.add_item cmd2

… should be:

toolbar.add_item(cmd2)

Indentation is 2 spaces in Ruby. Using more when showing code on the forum can cause readers to have to use horizontal scrolling to read code snippets.

It looks like your indentation is using TAB characters. With most good code editors, you can set TAB to be automatically replaced with a certain number of SPACE characters.
This is best when you post into forums or other posting on the web such as GitHub repository issues.
In Discourse engine forums, TAB characters for code is displayed as 4 wide. In GitHub, I think it may be 8 wide.


The line where a block begins should be aligned with line that closes the block.
Ie:

cmd2 = UI::Command.new("Haut") {
	deplacement_y_haut()
	}

should be:

cmd2 = UI::Command.new("Haut") {
  deplacement_y_haut()
}

… or … a one-liner:

cmd2 = UI::Command.new("Haut") { deplacement_y_haut() }

It looks like you wanted indentation to “group” the definition of your command objects for readability. You can use Ruby core’s #instance_eval to do this if you like. Within the block sent to this method, the command instance object is referenced as the reserved word self .
Ie:

cmd2 = UI::Command.new("Haut") {
	deplacement_y_haut()
	}	
	icon2 = File.join(__dir__, 'collierstoolbar', 'images','UP.png')
	cmd2.small_icon = icon2
	cmd2.large_icon = icon2
	cmd2.tooltip = "Haut"
	cmd2.status_bar_text = "Pour déplacer d'une trame vers le haut"
	toolbar.add_item cmd2

… can become:

cmd2 = UI::Command.new("Haut") {
  deplacement_y_haut()
}	
cmd2.instance_eval {
	icon2 = File.join(__dir__, 'collierstoolbar', 'images','UP.png')
	self.small_icon = icon2
	self.large_icon = icon2
	self.tooltip = "Haut"
	self.status_bar_text = "Pour déplacer d'une trame vers le haut"
}
toolbar.add_item(cmd2)

But I notice that in the command defintions, you’re getting the icon paths:

	icon2 = File.join(__dir__, 'collierstoolbar', 'images','UP.png')

Your extension’s code file should (and actually must) be placed within your extension’s subfolder.
So the 2nd argument … 'collierstoolbar' would be unneeded as __dir__() alone would return the correct path to your extension’s folder.

You should only have an extension registrar script in the "Plugins" folder that registers the extension and allows it to be switch on and off.

The naming convention for extension registrar files and extension folders is, the name of your top level namespace module, an underscore (_) and then the name of your extension.
Ex:
If your top level unique namespace you chose (for ALL of your extensions) was CollierMangrove and this test extension was wrapped in a submodule identified as ToolbarTest, …
then the extension subfolder in the "Plugins" folder should be "CollierMangrove_ToolbarTest",
and the extension’s registrar file should be "CollierMangrove_ToolbarTest.rb".

All of the extension’s actual running code should be in rb files inside the "CollierMangrove_ToolbarTest" subfolder. The only part in the "Plugins" folder proper should be the registrar file.

So, the code running in your extension most likely would never need to use a 'collierstoolbar' literal string because __dir__() and __FILE__ will return paths to the proper folder. (This means parts of your code can often reuse “generic” snippets that don’t use hardcoded literal extension subfolder name strings.)

1 Like