Learning Ruby and the SketchUp API

Continuing the discussion from LineTool example:

That’s because vertices is an Array of Vertex objects. You need to look back in the code to see where it is created and populated to realize this. Then if you look at the API docs for Vertex, you will see that it has a position method that returns a Point3d.

to_s is a basic Ruby method that tells an object to return a printable string representation of itself.

Your problem is again following the basic Ruby code. First look at the Entities class docs to see what add_face does (it creates and returns a Face object). Then look at the Face class docs to see what the vertices method does (it creates and returns an Array of Vertex objects). Now look at Vertex and you are on your way!

Can’t help you with that one - I have never heard of it (so far as I know it is neither standard Ruby nor SketchUp API, so if it exists it must have been defined somewhere in the code you are running).

Bottom line: it appears you need to study Ruby somewhat more to become better familiar with the language and syntax. Then worry about the SketchUp Ruby examples. They are indeed impenetrable if you don’t understand Ruby!

No, but the LanguageHandler’s square bracket method is documented, which does the same. You gave the ., but what is in front of the dot is the object which responds to this method (in object oriented programming methods are not global, but owned by classes). You look at the class of what object it is (LanguageHandler) and then look at the methods of that class. If you write a script for your own, you probably won’t need it, unless you want to build a localized and translated software package.

Not everything you see on the internet is also good to use (especially the example contains global variables and the camelCase methods contradicting ruby style). The mentioned method may not be officially deprecated, but the fact it’s not mentioned anymore lets us consider it not a public API method, that means developers can not rely on its continuous existence or behavior and therefore shouldn’t use it.

Yes but my point is that I can only look up in hindsight -so if I see, as in this case:

pent.vertices[0].position.to_s

I can look up vertices , position and to_s
But I can not look up vertices and then follow thru with possible uses because i have no information about what could follow it. That means that I have to know something of position and to_s first (or at least as in this case see them written in some code) before I can see that they can modify vertices.

-Sorry if I am confused or not clear.

Just seem to me that a thorough description of vertices would include position and all other applicable methods. I suppose that I may be wrong though. Maybe there are so many possible methods that act on vertices that it is not practical to list them.

Yes, it would just be convenient if I did not have to page back and forth -and as a newb and I would like to see the actual return to help me understand for example what a vertex object is.

Getstring, fileloaded? seems like a few others are common in the SU_examples scripts

That is most definitely true. I like learning by doing though but in this case I jumped way too far out ahead of myself. To paraphrase TopGun my coding was writing checks that my brain could not cash (or something like that)

You only need to worry about Getstring etc if you invoke a Langhandler and you have translations for the strings used in you code’s prompts etc…

One thing to get your head around is the difference between Vertex3d and Point3d.
Edges have Vertices [2]
Faces have Vertices [3 or more]
Faces have at least one Loop.
Loops have Vertices.
and so on…
A Vertex has a position, which is called a Point.

To complicate matters a three point array [x,y,z] will often be acceptable as defining a Point.

In many methods you can substitute Vertices for Points - e.g. adding a face from an array of vertices.

You can easily convert your vertices into points using .position.

You cannot directly compare Vertices, as they are separate entities, you can compare Points for equivalence, you can only compare point-arrays for being ‘the same’.

In terms of “transformations”… you can transform a point directly using:

point.transform!(transformation)

This also applies to ‘containers’ like group and component-instance…

But for raw geometry [edges, faces] and vertices you must use:

entities.transform_entities(transformation, array_of_entities)

or similar entities.transform… methods [note that this method also works on other ‘container’ entities, but not with point3d ]

Hmm. thanks TIG I ws just wondering how to add two 3dpoints together

thanks Aerilius
I found getstring would allow an input box to accept metric or imperial units and actually did not know it was some variation of LanguageHandler -it did not mention this ability.

Create the input box

prompts = [$exStrings.GetString("Width"), $exStrings.GetString("Depth"),
           $exStrings.GetString("Height")]
values = [4.feet, 4.feet, 4.feet]

results = inputbox prompts, values, $exStrings.GetString(“Cube”)

The LanguageHandler class and it’s [] instance method (alias GetString,) has nothing to do with the conversion of numerics between model units.

Also be aware that only strings that are IN the “strings” file loaded by the LanguageHandler::new constructor call can be successfully looked up. If the string is not in the file, the English argument is echoed back.

(In other words: LanguageHandler is a dumb class, and a simple wrapper around a Hash instance, whose members are loaded from a file.)


This unit conversion functionality is built-into the UI.inputbox features.
See Thomas Thomassen’s blog article:
Dealing with Units in SketchUp

Ahh, thanks DanRathbun,
So it looks like it was the fact that the input box was being fed unit information in the value default that actually allowed it to accept metric and had nothing to do with getstring.

-another example of incomplete information in the reference material.

Specifically if you feed the defaults Length class objects, they’ll get properly returned in the result array.

Read up on the Numeric class additions by the API. The feet instance method is one of them that converts and returns a Length object.

I do not understand why basic mouse functions are being handled in ruby.

I can see why users need the ability to override behavior but the performance of linetool.rb is noticeably worse than the system pencil.

FredoTools DrawAlong works better but is still laggy

Is there a better way to handle mouse functions?
Seems like I should just be able to send the system mouse point and clicks to my tool.

The Tool class covers all aspects of handling mouse location, clicking, button/key-presses, typed input etc etc…
http://www.sketchup.com/intl/en/developer/docs/ourdoc/tool
There are additional pick-helpers, temporary graphics drawing methods etc, too…
You last sentence makes little sense ??
Your tool when properly coded and set up gets its own data on the system mouse and more besides ?
Many of us have successfully used the API’s Tool toolset in many plugin-tools…

I am not questioning whether or not Ruby provides the tools to handle mouse functions because the answer to that is obvious. Well maybe i am confused then because it seems to me that it uses ruby.
That is what most of this code is:

 Copyright 2012, Trimble Navigation Limited

 This software is provided as an example of using the Ruby interface
 to SketchUp.

 Permission to use, copy, modify, and distribute this software for 
 any purpose and without fee is hereby granted, provided that the above
 copyright notice appear in all copies.

 THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-----------------------------------------------------------------------------

require 'sketchup.rb'

-----------------------------------------------------------------------------

module Sketchup::Examples

 To create a new tool in Ruby, you must define a new class that implements
 the methods for the events that you want to respond to.  You do not have
 to implement methods for every possible event that a Tool can respond to.

 Once you have defined a tool class, you select that tool by creating an
 instance of it and passing it to Sketchup.active_model.select_tool

 This implementation of a tool tries to be pretty complete to show all
 of the kinds of things that you can do in a tool.  This makes it a little
 complicated.  You should also look at the TrackMouseTool defined in
 utilities.rb for an example of a simpler tool.

 This example shows the implementation of a simple line tool.  This tool
 is similar to the Pencil tool in SketchUp, but it create construction lines
 rather than edges

class LineTool

 This is the standard Ruby initialize method that is called when you create
 a new object.
def initialize
    @ip1 = nil
    @ip2 = nil
    @xdown = 0
    @ydown = 0
end

 The activate method is called by SketchUp when the tool is first selected.
 it is a good place to put most of your initialization
def activate
    The Sketchup::InputPoint class is used to get 3D points from screen
    positions.  It uses the SketchUp inferencing code.
     In this tool, we will have two points for the end points of the line.
    @ip1 = Sketchup::InputPoint.new
    @ip2 = Sketchup::InputPoint.new
    @ip = Sketchup::InputPoint.new
    @drawn = false

     This sets the label for the VCB
    Sketchup::set_status_text $exStrings.GetString("Length"), SB_VCB_LABEL
    
    self.reset(nil)
end

 deactivate is called when the tool is deactivated because
 a different tool was selected
def deactivate(view)
    view.invalidate if @drawn
end

 The onMouseMove method is called whenever the user moves the mouse.
 because it is called so often, it is important to try to make it efficient.
 In a lot of tools, your main interaction will occur in this method.
def onMouseMove(flags, x, y, view)
    if( @state == 0 )
         We are getting the first end of the line.  Call the pick method
         on the InputPoint to get a 3D position from the 2D screen position
         that is passed as an argument to this method.
        @ip.pick view, x, y
        if( @ip != @ip1 )
             if the point has changed from the last one we got, then
             see if we need to display the point.  We need to display it
             if it has a display representation or if the previous point
             was displayed.  The invalidate method on the view is used
             to tell the view that something has changed so that you need
             to refresh the view.
            view.invalidate if( @ip.display? or @ip1.display? )
            @ip1.copy! @ip
            
             set the tooltip that should be displayed to this point
            view.tooltip = @ip1.tooltip
        end
    else
         Getting the second end of the line
         If you pass in another InputPoint on the pick method of InputPoint
         it uses that second point to do additional inferencing such as
         parallel to an axis.
        @ip2.pick view, x, y, @ip1
        view.tooltip = @ip2.tooltip if( @ip2.valid? )
        view.invalidate
        
         Update the length displayed in the VCB
        if( @ip2.valid? )
            length = @ip1.position.distance(@ip2.position)
            Sketchup::set_status_text length.to_s, SB_VCB_VALUE
        end
        
         Check to see if the mouse was moved far enough to create a line.
         This is used so that you can create a line by either dragging
         or doing click-move-click
        if( (x-@xdown).abs > 10 || (y-@ydown).abs > 10 )
            @dragging = true
        end
    end
end

 The onLButtonDOwn method is called when the user presses the left mouse button.
def onLButtonDown(flags, x, y, view)
     When the user clicks the first time, we switch to getting the
     second point.  When they click a second time we create the line
    if( @state == 0 )
        @ip1.pick view, x, y
        if( @ip1.valid? )
            @state = 1
            Sketchup::set_status_text $exStrings.GetString("Select second end"), SB_PROMPT
            @xdown = x
            @ydown = y
        end
    else
        # create the line on the second click
        if( @ip2.valid? )
            self.create_geometry(@ip1.position, @ip2.position,view)
            self.reset(view)
        end
    end
    
     Clear any inference lock
    view.lock_inference
end

# The onLButtonUp method is called when the user releases the left mouse button.
def onLButtonUp(flags, x, y, view)
     If we are doing a drag, then create the line on the mouse up event
    if( @dragging && @ip2.valid? )
        self.create_geometry(@ip1.position, @ip2.position,view)
        self.reset(view)
    end
end

 onKeyDown is called when the user presses a key on the keyboard.
 We are checking it here to see if the user pressed the shift key
 so that we can do inference locking
def onKeyDown(key, repeat, flags, view)
    if( key == CONSTRAIN_MODIFIER_KEY && repeat == 1 )
        @shift_down_time = Time.now
        
         if we already have an inference lock, then unlock it
        if( view.inference_locked? )
            calling lock_inference with no arguments actually unlocks
            view.lock_inference
        elsif( @state == 0 && @ip1.valid? )
            view.lock_inference @ip1
        elsif( @state == 1 && @ip2.valid? )
            view.lock_inference @ip2, @ip1
        end
    end
end

 onKeyUp is called when the user releases the key
 We use this to unlock the inference
 If the user holds down the shift key for more than 1/2 second, then we
 unlock the inference on the release.  Otherwise, the user presses shift
 once to lock and a second time to unlock.
def onKeyUp(key, repeat, flags, view)
    if( key == CONSTRAIN_MODIFIER_KEY &&
        view.inference_locked? &&
        (Time.now - @shift_down_time) > 0.5 )
        view.lock_inference
    end
end

 onUserText is called when the user enters something into the VCB
 In this implementation, we create a line of the entered length if
 the user types a length while selecting the second point
def onUserText(text, view)
     We only accept input when the state is 1 (i.e. getting the second point)
     This could be enhanced to also modify the last line created if a length
     is entered after creating a line.
    return if not @state == 1
    return if not @ip2.valid?
    
     The user may type in something that we can't parse as a length
     so we set up some exception handling to trap that
    begin
        value = text.to_l
    rescue
         Error parsing the text
        UI.beep
        puts "Cannot convert #{text} to a Length"
        value = nil
        Sketchup::set_status_text "", SB_VCB_VALUE
    end
    return if !value

     Compute the direction and the second point
    pt1 = @ip1.position
    vec = @ip2.position - pt1
    if( vec.length == 0.0 )
        UI.beep
        return
    end
    vec.length = value
    pt2 = pt1 + vec

     Create a line
    self.create_geometry(pt1, pt2, view)
    self.reset(view)
end

 The draw method is called whenever the view is refreshed.  It lets the
 tool draw any temporary geometry that it needs to.
def draw(view)
    if( @ip1.valid? )
        if( @ip1.display? )
            @ip1.draw(view)
            @drawn = true
        end
        
        if( @ip2.valid? )
            @ip2.draw(view) if( @ip2.display? )
            
             The set_color_from_line method determines what color
             to use to draw a line based on its direction.  For example
             red, green or blue.
            view.set_color_from_line(@ip1, @ip2)
            self.draw_geometry(@ip1.position, @ip2.position, view)
            @drawn = true
        end
    end
end

 onCancel is called when the user hits the escape key
def onCancel(flag, view)
    self.reset(view)
end


 The following methods are not directly called from SketchUp.  They are
 internal methods that are used to support the other methods in this class.

 Reset the tool back to its initial state
def reset(view)
    # This variable keeps track of which point we are currently getting
    @state = 0
    

    # clear the InputPoints
    @ip1.clear
    @ip2.clear

Most of this code simply replaces or duplicates code that has already been done at the system level. And if you run the the program you can tell that it is ruby. Even Fredo’s better written DrawAlong tool lags.

But perhaps my understanding of programming is seriously deficient because I do not see why I need to do all this in ruby when it is already done at the system level where it runs more efficiently.

FYI, Ruby is an interpreter. All interpreters are slow, compared to compiled code.

But like all interpreters, they themselves need to be programmed in some manner. Ruby is implemented in C/C++.

SketchUp’s interface to Ruby is likewise. SketchUp’s Ruby API is 99% implemented in compiled C/C++.

So if you do not care for Ruby,… you can write and compile your extensions in C/C++. And make direct system calls using system libraries, if you know how. The drawback is that you need to understand both OSX and Windows system functions, and have code that accommodates each. (Ruby and SketchUp API functions already do this platform accommodation for Ruby extensions.) Another issue is that two binaries need to be compiled for Windows, a 32-bit and 64-bit. (A Ruby extension, usually, need not worry about platform bitness.)

So, in end it is you that must choose your poison.

Yeah I am already over my head with Ruby -much less C++

I guess that answers the question though -no access to the more efficient mouse which is too bad because it is a real resource hog.

Thanks all.

It may not be the actual mouse calls. I’ve done some debugging in the past that showed as you move the mouse around, (ie as a tool instance is waiting for user input,) SketchUp’s UI redraw function is firing. It is redrawing all the toolbar buttons, and calling each buttons validation proc (to know whether to draw it pressed, unpressed, or grayed out.) Some coders write slow code that has complex boolean expressions in these procs, which added together slow things down. So the more toolbars that are open, the slower things can seem. If the Outliner window is open again it will slow things down, the more objects are in the model, the time to update the outline tree. The more other tool windows that are open, the more UI there is to refresh on each cycle.

Yes, they mention in the comments that it is important to make the code efficient.

-I just wish the example provided worked well and displayed best practices. No telling how long it will take me to figure out what is going on there.

This should get you started, until I can re-organize and update my Newbie’s Guide and Ruby Reference lists.


Tutorials (do in this order)

Ruby in Twenty Minutes
[url]https://www.ruby-lang.org/en/documentation/quickstart/[/url]

Ruby User’s Guide by Prof. Mark Slagell
[url]http://www.rubyist.net/~slagell/ruby/[/url]

Introduction to Ruby
[url]http://ruby-doc.org/docs/Tutorial/[/url]


Online Books

Programming Ruby: The Pragmatic Programmer’s Guide by Dave Thomas
The 'ol “Pick Axe” book. It is a must read. (There are newer editions available.)
[url]http://phrogz.net/ProgrammingRuby/frameset.html[/url]

Learn to Program by Chris Pine
[url]https://pine.fm/LearnToProgram/[/url]

Mr. Neighborly’s Humble Little Ruby Book
[url]http://www.humblelittlerubybook.com/[/url]


Downloadable

The Little Book Of Ruby by Huw Collingbourne
[url]http://www.sapphiresteel.com/IMG/pdf/LittleBookOfRuby.pdf[/url]

The Book Of Ruby by Huw Collingbourne
[url]http://www.sapphiresteel.com/IMG/zip/book-of-ruby.zip[/url]

And there is also the only book on SketchUp and Ruby: “Automatic SketchUp” by Matthew Scarpino.
Available for free download in searchable PDF here:
http://www.crai.archi.fr/rld/pdf/Automatic_SketchUp.pdf

Also on Amazon for $249(???) in paperback but why would you do that?

… which is the LAST book a newbie should ever read ! (It has serious issues with best practices.)

1 Like

Since I might be following those worst practices, please enlighten me.