Please do not put a space between the method identifier and it’s parameter list.
This will cause warnings output to the console and/or warnings from Rubocop if you are using it.
Ruby uses 2 space indents. Using larger indentation causes readers of your code to do excessive horizontal scrolling when you post in the forum.
Also many forums and GitHub will use 8 spaces for TABs. So it’s best to have your code editor automatically replace TABs with 2 spaces for .rb files. (Good editors allow different settings for different coding languages. (4 for .py files, 8 for .c,.cpp,.h, etc.)
Ruby does not force the use of frivolous parenthesis around each conditional expression. Ie:
The convention is to use them only when it is necessary to force order of evaluation.
Of course you do, because literal strings must be enclosed in some kind of quotation.
But it is acceptable to set a reference to a string and use this reference in the call.
comp = 'test'
m(comp, [10,10,10])
Because you did not properly quote the string, the Ruby interpreter looked for an object identified with the reference test and found it was the global method #test, from module Kernel, which is mixed into class Object which is the top level ObjectSpace called main. (Ruby is implemented in C so it’s interpretive program loop is the C function main().)
So, because Kernel#test has a minimum of 2 and maximum of 3 arguments and your statement …
m test, [10,10,10]
… did not pass any arguments in a parameter list with test (ie, it was followed immediately by a comma,)
…
you get an ArgumentError that correctly informs you that you passed 0 args instead of between 2…3 to the method test (the method name always follows “in” for error messages.)
The second blank “:in ‘’ SketchUp” means it was at the top level. (Normally this would give the Ruby filename followed by the line number.)
Incorrect nomenclature. A Ruby tool is a code object that implements abstract SketchUp::Tool class interface. It is a tool that the user interacts with using the cursor (usually a tool specific cursor icon or icons,) in order to manipulate the model.
What you describe is more of a utility. A command that runs, does it’s thing and finishes.
Your not the first to try this. However, SketchUp Ruby is a shared environment. Ruby’s top level namespace is a special instance of Object. Everything in Ruby is an object, therefore a subclass of Object. Whatever you define at the top level becomes global and every other object (classes and modules) of Ruby’s and the SketchUp API and all other author’s extension objects will inherit what you define at the the top level.
So everything (using your example) is going to inherit a m() method. This is unacceptable in a shared environment.
You are going to run into clashes when you try to define a p(), pp(), j() and jj() methods because these are already globally defined. If you defined these you redefine them and can cause havoc with other extensions.
This is why the number 1 rule of SketchUp coding is stay within YOUR namespace module.
Every author needs to invent a unique top level namespace module name. Your name or your company’s name. Then each of your extensions need to be separated into it’s own submodule inside your top level namespace module, so they do not interfere with one another.
Dan,
Thankyou for taking the time for such a detailed response. I really appreciate it. I said I was new to Ruby and I guess it shows . I would like to comment on your various points below. Hopefully I get the quoting right. (Not sure how to use the block quote but I will try.) Hopefully it does not mess up too badly…
I was aware of these and have actually accessed them and am working through them. And thanks for the tip on the colorized code and text back ticks.
Noted for the single quotes imbed and the tab spacing. I use Notepad++ and Textpad for my text editing. Both have tab setting and colorization capabilities based on the file extension. I will use two spaces for tabs from here on, thanks. I am new to notepad++ but have used Textpad for years. Beginning to think I like Notepad better.
As regards the space between the method id and the param list, This is easy to comply with. will do. I assume Rubocop is a play on the TV movie. And no I don’t use it but I will google it and check it out. Anything that helps with my coding is a plus.
[quote=“DanRathbun, post:6, topic:190140”]
Ruby does not force the use of frivolous parenthesis around each conditional expression. Ie:
The convention is to use them only when it is necessary to force order of evaluation.
Noted: I am still experimenting with this for readability, etc.
I wondered about this but could not quite figure out how to implement it properly. I appreciate the coding tips and explanation
You are correct that this is more a utility then a command. This was my intent I just did not know the proper way to describe it. (still learning here)
Your comments on SketchUp Ruby and shared environments concern me. I certainly do NOT want to impact this shared environment, or other users with my code, nor do I want my code impacted by this.
At the moment, I am only running my utilities file via Sketchup Make and its Ruby console. I think I see where I might impact the Sketchup environment in my local session of Sketchup, and possibly code implemented by any extensions I am using. I am unclear how this could go farther. I (currently) only run my bst.rb file from the console and it is not in the sketchup plugins subdirectory so it does not load automatically with Sketchup. I HAD planned do just that if I found enough usefulness in the utils. might want to rethink this. Right now its a means for learning Ruby.
Can you explain more about creating my own namespace and how I might accomplish this an run my utils and any future tools I may write without risking impact on other code. My reading to date has not yet touched on namespaces or creating submodules.
One last question. when I invoke the utility m() with
m 'test', [10,10,10]
the console responds with
m 'test', [10,10,10]
# of args 2
args[0] is test
args[1] is [10, 10, 10]
Sketchup ComponentInstance is test
Sketchup::ComponentInstance is false
which is correct according to the code, but WHY does the component instance test come back with FALSE? I created the component and gave it the name test. How should I access/name the component so that args[0] is the name of the component I want to move?
Again, thank you for your advice and for taking time to help me.
Brad Smith
You are using a boolean method for that line. BUT … what you posted above is incomplete:
puts 'Sketchup::ComponentInstance is ' +args[0].is_a?(
… there is no class identifier and closing ")".
If you want to test what type the args are, then use:
puts '# of args : '<< args.length.to_s
puts "args[0] is (#{args[0].class}): #{args[0].inspect}"
puts "args[1] is (#{args[1].class}): #{args[1].inspect}"
This uses string interpolation with code enclosed in #{} within double-quoted strings.
This also avoids the creation of an extra String object that occurs with String#+.
Whenever you need to append stuff to the end of a string and it is okay to modify that string (because it’ll be GC'd at the end of the method,) then favor String#<<.
Now, again looking at the code, is see in messagbox text via a type sniffing saying the first arg needs to be a instance reference.
When I first read your post, you explained how the m() method worked. I thought you had said the first argument was to be the instance name. So I assumed the first arg needed to be a String that you would assign to the instance’s name property via ComponentInstance#name().
But apparently, you meant an object reference to an existing instance. This is far different from a value stored in an object’s internal data that just happens to have the key “name”.
So let us step back. IF you have created the cuboid component and named it “Test” (internal property) and it’s new instance is selected, you can assign an object reference using Ruby’s interpretive assignment operator (=) like so …
model = Sketchup.active_model
test = model.selection[0]
test is now a local reference (which happens to be global) pointing at your new instance object.
Now, as said previously, there is also a global method that has an identifier “test”.
Now you call your method m(test, [10,10,10]) … what does Ruby do ?
The answer is that it first looks for local references, so it should find and pass your object reference test to the m() method, rather than attempting to call the Kernel#test method.
So originally, you were confounding the idea of a data name property with an object reference identifier. Two separate and distinct things.
Revisiting what your intentions were …
Okay, I was correct, you had already given the instance a name, and you want the first arg to be the string name.
That can be done rather than pass a reference, you want the m() method to find the reference.
Easy-Peezy …
You said that the new instance was selected, so it’s even easier thus:
It’s simple. You create a top level namespace module and use it for ALL of you extensions, even be they little utilities.
module BDS
module NiftyUtility
extend self
# Local Constant definitions
# Module variables
# Local Class definitions
# Methods
# Rub Once at Startup code
if !defined?(@loaded)
# Define submenus, commands, menu items and toolbars
@loaded = true
end
end # submodule
end # namespace module
The interpreter sees a module Identifier statement and will determine if such module already exists. If not, it basically creates a new one via a call to Identifier = Module.new.
But if such a module already exists, then it will open it for edits and additions via Identifier.module_eval.
This means you can open and modify any module, any number of times, in any number of files.
In this way a developer can break up the code for large extensions across multiple files.
This is what’s called a dynamic programming language, where code objects can be modified during runtime.
Well at the least this utilities methods could be wrapped into a BST module. Ie …
module BST
extend self
def m(*args)
#Invoke with "m comp_name, [r,g,b], repeat_count <,optional>"
# example "m test, [10,10,30], 10"
puts '# of args : '<< args.length.to_s
puts "args[0] is (#{args[0].class}): #{args[0].inspect}"
puts "args[1] is (#{args[1].class}): #{args[1].inspect}"
unless args.length > 0 && args[0].is_a?(Sketchup::ComponentInstance)
UI.messagebox(
'First argument of "m()" must be a Sketchup::ComponentInstance.'<<
"\n"<<'Use "xm()" to translate by r, g, b values.'
)
return
end
case args.length
when 2 then move( args ) # static move
when 3 then movie( args ) # animated move
when 1 then xmove( args[0] )
else UI.messagebox(
'm args must be: ( transformation ) or ( comp, vec ) or ( comp, vec, ntimes ).'
)
end
end # of m()
end
It won’t go further unless you release code without module wrapping it.
And if you do not run many other extensions from other authors, then you might not see any impact.
Yeah, I know. Somehow when I copy pasted the code from the editor I truncated that line to what you see. And then I saved it and just spent 10 minutes figuring out what I deleted and putting it back. It used to work. Fat Fingers (sigh). What it DID say (and now does again) is:
puts 'Sketchup::ComponentInstance is ' +args[0].is_a?(Sketchup::ComponentInstance).to_s
Useful info. Thanks.
I did say it was the instance name and I can see why you assumed I wanted to use it to name the component. And as you pointed out on reflection, I actually want to reference the existing component ‘test’ (now renamed ‘testblock’, Please don’t tell me this is also global ) Thanks for the correction. I want to do all this from within the m() utility so I will add the lines
model = Sketchup.active_model
args[0] = model.selection[0]
within the m() utility but before I do anything else.
I think this gives me the following in my code:
def m(*args)
#Invoke with "m comp_name, [r,g,b], repeat_count <,optional>"
# example "m test, [10,10,30], 10"
puts '# of args ' +args.length.to_s
puts 'args[0] is ' +args[0]
puts 'args[1] is ' +args[1].to_s
puts 'Sketchup ComponentInstance is ' +args[0].to_s
model = Sketchup.active_model
args[0] = model.selection[0]
puts 'Value of comp is '+args[0].to_s
puts 'Sketchup::ComponentInstance is ' +args[0].is_a?(Sketchup::ComponentInstance).to_s
unless ( args.length > 0 ) && ( args[0].is_a?(Sketchup::ComponentInstance) )
UI.messagebox( "First argument of \"m()\" must be a Sketchup::ComponentInstance.\n" +
'Use "xm()" to translate by r, g, b values.')
return
end
=begin commented out until i get the parameter passing to work
case args.length
when 2 then move( args ) # static move
when 3 then movie( args ) # animated move
when 1 then xmove( args[0] )
else UI.messagebox( 'm args must be: ( transformation ) or ( comp, vec ) or ( comp, vec, ntimes ).' )
end
=end
end # of m()
I did this and this is the result.
m 'testblock', [10,10,10]
# of args 2
args[0] is testblock
args[1] is [10, 10, 10]
Sketchup ComponentInstance is testblock
Value of comp is #<Sketchup::ComponentInstance:0x00022aa94c5600>
Sketchup::ComponentInstance is true
so now it sees testblock as a component. now let’s see if I can get it to move.
Didn’t see the above until after I did the above new code. Have to give your version a try. Its all good. I need to learn this stuff.
Namespace stuff
I have to think about this some. sounds pretty cool though.
This i will do first. that way I keep my “NiftyUtilities” (love that name) from impacting others. Then my own namespace for ALL my new code.
It is unlikely I will release ANY code for a while. Just now I am too nervous about my own state of knowledge. You know, A little knowledge is a dangerous thing. I do run a few extensions so wrapping bst.rb with NiftyUtilities code as you describe will be done.
Brad
replaced “testblock” with a bunch of hex as I added some puts.
puts '# of args ' +args.length.to_s
puts 'args[0] is ' +args[0]
puts 'args[1] is ' +args[1].to_s
puts 'Sketchup ComponentInstance is ' +args[0].to_s
model = Sketchup.active_model
args[0] = model.selection[0]
puts 'model.selection is' +model.selection[0].to_s
puts 'name of component is '+args[0].to_s
puts 'Sketchup::ComponentInstance is ' +args[0].is_a?(Sketchup::ComponentInstance).to_s
and it came back with a bunch of hex.
m 'testblock', [10,0,0]
# of args 2
args[0] is testblock
args[1] is [10, 0, 0]
Sketchup ComponentInstance is testblock
model.selection is#<Sketchup::ComponentInstance:0x00022aa97873e8>
name of component is #<Sketchup::ComponentInstance:0x00022aa97873e8>
Sketchup::ComponentInstance is true
#<Sketchup::View:0x00022aa96a1f00>
But it moved!!! YAY
according to Entity Info in SU tray I named the definition. Should I have named the instance? is that why I got hex back instead of testblock? My puts that returned testblock should have read
puts 'arg[0] is ' +args[0].to_s
instead of
puts 'Sketchup ComponentInstance is ' +args[0].to_s
I just repeated the earlier puts for args[0]. Oh well. live and learn.
So…to be able to use testblock as the actual name of what is to be moved (rather than the hex (i assume) Instance, what do I do?
BTW you are being very patient with me. It is appreciated.
That’s a Ruby inspection string giving class identifier and apparently a hex encoding of the integer Object#object_id.
I would encourage you to use #inspect rather than #to_s.
No. You are using #to_s on an object reference. Object#to_s says:
Returns a string representing obj . The default to_s prints the object’s class and an encoding of the object id. As a special case, the top-level object that is the initial execution context of Ruby programs returns “main’’.
SketchUp’s API says it overrides the #to_s method from Object in class Sketchup::Entity, but I think it only changes the output for some subclasses.
This is what I thought as the name field at the top of the Make Component dialog applies to the definition.
And you need to give the definition a unique name because the name is the key in the DefinitionList collection. Otherwise SketchUp will make up a generic unique name that does not mean much, like "Component#4".
It depends upon IF you wish to find and identify it specifically. Which is what you original idea implied.
All definitions must have a name property as it is a key into the collection. But instances can have a empty name property. A definition can have multiple instances. Each can have it’s own name, or the same name. It’s just a text data property for instances.
You can find a certain definition in the DefinitionList collection, like:
cdef = model.definitions['testblock'] # nil if not found
Then you can find a specific instance of that definition in several ways.
I am going to quit for a while, I am getting tired. I thought when I retired as an electrical engineer and took up woodworking I was done with programming. Never did it much. Mainly as an adjunct to hardware design (my forte) for hardware testing and VHDL and Verilog coding for FPGAs. I am enjoying myself though.
I’ll get back to this tomorrow. You have a good night.
Brad
Dan
Good Afternoon,
I tried putting my utilities in a module as you described. After re-starting SU I loaded the bst.rb file from the console and it appeared my various methods were ignored as nothing happened when I ran the m() utility.
Here is what I had code-wise:
# /Rubies/bst.rb -- temporary test file
require 'sketchup'
puts( "Loading Brad's bst" ) #test msg
module BST
extend self
load File.dirname( __FILE__ )+'/bst_classes.rb'
puts( 'Brad''s bst loaded') #test msg
# ******************************************************************
# SketchTalk Functions ------------------------------------------
# ****************************************************************
def all()
#UI.messagebox( ' all here ' ) #test msg
model = Sketchup.active_model()
model.selection().clear()
ents = []
for e in model.entities()
ents.push(e) if e.layer().visible?()
end
model.selection().add(ents)
end # of all()
def box( near, far, pushpull )
b = Box.new( near, far, pushpull )
b.draw()
return b
end # of box()
# followed by a bunch of of other method defs until the m() util
def m(*args)
#Invoke with "m 'comp_name', [r,g,b], repeat_count <,optional>"
# example "m 'testcomp', [10,10,30], 10"
puts 'arguments as passed'
puts '# of args : '<< args.length.to_s
puts "args[0] is (#{args[0].class}): #{args[0].inspect}"
puts "args[1] is (#{args[1].class}): #{args[1].inspect}"
puts '# of args ' +args.length.inspect
puts 'args[0] is ' +args[0].inspect
puts 'args[1] is ' +args[1].inspect
puts 'Sketchup ComponentInstance is ' +args[0].inspect
model = Sketchup.active_model
args[0] = model.selection[0]
puts 'model.selection is ' +args[0].inspect
puts 'name of component is '+args[0].inspect
puts 'Sketchup::ComponentInstance is ' +args[0].is_a?(Sketchup::ComponentInstance).inspect
unless args.length > 0 && args[0].is_a?(Sketchup::ComponentInstance)
UI.messagebox( "First argument of \"m()\" must be a Sketchup::ComponentInstance.\n" +
'Use "xm()" to translate by r, g, b values.')
return
end
case args.length
when 2 then move( args ) # static move
when 3 then movie( args ) # animated move
when 1 then xmove( args[0] )
else UI.messagebox( 'm args must be: ( transformation ) or ( comp, vec ) or ( comp, vec, ntimes ).' )
end
end # of m()
# and a bunch more method defs then some support function icluding this one
def move( args ) # static move
inst = args[0]
trans = translate( inst.transformation, args[1] )
inst.move!( trans )
draw()
end # of move()
#And finally the module end
end #of module /Rubies/bst.rb
If I comment out the module, extend self and end statements, then reload, it all works again.
load '/rubies/bst.rb'
Loading Brad's bst
Brads bst loaded
true
m 'testblock',[10,10,10]
arguments as passed
# of args : 2
args[0] is (String): "testblock"
args[1] is (Array): [10, 10, 10]
# of args 2
args[0] is "testblock"
args[1] is [10, 10, 10]
Sketchup ComponentInstance is "testblock"
model.selection is #<Sketchup::ComponentInstance:0x0001b12ebd92b0>
name of component is #<Sketchup::ComponentInstance:0x0001b12ebd92b0>
Sketchup::ComponentInstance is true
#<Sketchup::View:0x0001b1340fc918>
box [0,0,0],[10,10,10],25
#<Box:0x0001b1340fc3a0>
(the box util is just another method I wanted to see if worked. it did)
What did i do wrong?
Brad
(Nag): This is basic Ruby. By now you should have read several of the free downloadable books I list in the Ruby Learning Resources list.
You must make qualified method calls from outside the module. Ie …
BST.m('testblock', [10,10,10])
… and …
BST.box([0,0,0], [10,10,10], 25)
If for example you think typing capital BST is slowing you down, then temporarily you can create a local (global) reference to point at your BST module, like so …
x = BST
And now for the rest of the session you can do …
x.m('testblock', [10,10,10])
… and …
x.box([0,0,0], [10,10,10], 25)
… etc., …
I chose “x” as it does not seem that it is a preset keyboard shortcut. It is likely that you’ll forget to focus the console window from time to time and be hitting the reference letter when the app window has focus. If it’s not set for a shortcut then this booboo won’t effect anything. (For example, I didn’t suggest “z” because it’s set for the Zoom tool.)
The other alternative is to code up a UI.inputbox that takes you method call as a string, and passes it to your module. Then create a menu item to bring up the inputbox and assign whatever shortcut you want to that menu command. (CTRL+i)
I am familiar with this. I have been following Martin Rineharts tutorial “Edges to Rubies” in producing this code. I ran into my original difficulty with his m() method and was trying to understand what was happening. I did not intend to claim credit for the code as mine. its not. but i have been altering it as I ran through the tutorial to suit myself.
It may be basic Ruby but i have only been trying to learn ruby for about a week and have only been using SU about a month. I am reading “Automatic Sketchup” by Matthew Scarpino currently and have looked at several of the other books in your lists.
Ok this is helpful. And it makes sense when I think about how other SU Ruby code gets written, eg.
Sketchup.active_model.entities
Since SU is a module. (Just learned that today)
I did that "BST.m() call and it works. thanks
( as a side note, why does a block comment have to start at the beginning of a line, no spaces in front.)
=begin
misc. comments
=end
if I have any spaces or tabs in front of the =begin, it doesn’t see it as a comment
I did change things to use #inspect rather than .to_s so thanks for that advice. I still can’t get
m 'testblock' [10,10,10]
to work directly as the args[0] parameter. I have to use
model = Sketchup.active_model
args[0] = model.selection[0]
Well now that explains a lot of why you are having so much trouble.
If you look at my Tutorials list in the Resources wikis, you’ll see I strongly discourage it’s use in big red typeface.
Martin was a Java programmer, that just did not really understand Ruby’s paradigms.
He often proposed that SketchUp should abandon Ruby and use Java in a long debate topic where the Rubyists had to defend it’s use from others who proposed various other scripting languages.
Anyway, several times he posted and proposed modifying the global ObjectSpace for specific utility, not understanding his code was modifying everything. I called him on this several times. As far as I know he never corrected his online tutorial to “play nice” with SketchUp’s shared Ruby process. And this is why I added the warning.
I think that now is the time to remove it from the tutorial resource list.
Listen, I am moving on. I don’t want any more to do with this or any edition of that codebase.
I have arthritis in my hands and all this typing is about problems that would not occur if ya fully read a good book on Ruby like the ol’ Pick Axe book, and get a grasp of the basic fundamentals of Ruby.
Unfortunately, this is not one of the first books you should read. It is similar to Martin’s work in that Matthew does not teach coding that is correct for a shared Ruby environment. It really needs an update.
The module concept is the 2nd thing I teach after describing how Ruby is object oriented.
It is just the way the Ruby interpreter is coded to work.
Also I do not use them myself and many other do not.
I use blocks of # lines. Most good code editors can collapse contiguous comment lines.
You are not selecting it. That is something for the end user to do manually.
With the API all you need to do is reference an object. The visible selection that the user sees is not needed for manipulating model objects with the API.
So, again, if you wish to find (and reference) an object by a string name, use the searching statements I gave above.
def m(*args)
model = Sketchup.active_model
ents = model.active_entities
if args[0].is_a?(String)
instset = ents.grep(ComponentInstance)
# search for the 1st instance whose name is args[0]
inst = instset.find {|i| i.name == args[0] }
if inst # an instance was found
# use this inst
else
# search for the 1st instance whose definition name is args[0]
inst = instset.find {|i| i.definition.name == args[0] }
if inst # an instance was found
# use this inst
end
end
else # args[0] is some other type
# ... etc ...
end
# ... etc ...
end # method m()
ADD: You can add in a conditional test if you still want to use the visible selection,
whether the inst reference found is actually selected in the viewport …
I don’t mean to beat ya’ up, but these are the same issue debates I had with Martin, Matthew and numerous others (newbs and gurus both) beginning about 2009. I get weary of retyping these over and over.
Dan
No worries. I did not mean to re-open old arguments. I really am trying to learn this stuff. I came across Martin’s and Mathew’s books and both looked comprehensive and focused on ruby for sketchup. So I am reading them and trying their examples. They may well be outdated but they are easier to follow then trying to puzzle out example code such as that in the GitHub tutorials when my knowledge base is currently at preschool levels.
You provided much more help with your explanations and enabled me to get things working again when I was quite stumped.
I understand about not wanting to keep repeating the same lessons and debates but that is the territory of someone who teaches. And arthritis is not a joke, I know.
I will continue my learning efforts and would welcome your help. Especially if you know of a current tutorial on Ruby specific to Sketchup that starts at the elementary and progresses thru advanced.