Save coordinates in an array
-
Hi all,
I'm building a tool that should clone the line tool, but it should also save clicked coordinates in an array.
So in other words, the user would click some points on the screen, a line should be drawn connecting the points he/she clicked, but also save these points in an array.
I'm pretty sure it's easy, but I'm struggling with it.Thanks in advance,
Mike -
As the tool starts [before the first point is clicked - probably in one of either the initialize or activate or reset methods [if you have that] which will setup an empty array to contain the points.
@pts=[]
When the user clicks each point to add new geometry simply use
@pts << @pt
to add the point to the array... assuming that this point is referenced by@pt
...Now on completion you have an array of points as clicked by the user, which is called
@pts
- now you can do whatever you want with the list...A 'plain' reference - e.g.
pts
- is only seen in its method [def
] [or 'block' (e.g.pts.each{|pt|puts pt}
unless it was declared beforehand outside of that block - e.g.pt=nil;pts.each{|pt|puts pt};puts pt
which would leavept
as the last item in the arraypts
after the array has been printed to the Ruby Console]).
A '@pts
' reference can be used across methods within that instance of the class - i.e. the one use of the tool...
While '@@pts
' references are remembered with the class - in this case to remember ALL points picked while the use of your tool [perhaps repeatedly] during that Sketchup session, you'd declare@@pts=[]
directly inside the body of the class, i.e. outside of any of the methods, but you can still push elements into it inside any of the class methods, using@@pts<<@pt
etc... -
Hi TIG,
Thanks for your reply, but I'm still quite confused.mod = Sketchup.active_model # Open model ent = mod.entities # All entities in model sel = mod.selection # Current selection class MyTool def activate puts "Your tool has been activated." @pts=[] end def onLButtonDown(flag, x, y, view) ip1 = view.inputpoint x,y @pt = ip1.position @pts << @pt end end my_tool = MyTool.new Sketchup.active_model.select_tool my_tool onLButtonDown(1,100,100,Sketchup.active_model.active_view) pts.each{|pt|puts pt}
I get this error:
Error: #<NoMethodError: undefined method `onLButtonDown' for main:Object>
Sorry I'm still new. -
The simpler
Sketchup.active_model.select_tool(MyTool.new)
needs to go into a menu-item after the class code - otherwise it just runs at startup !
There are many examples of how to add a command to the Plugins menu...
OR temporarily while testing just set it tomytool=Sketchup.active_model.select_tool(MyTool.new)
outside of the class and run it from the Ruby Console by typing 'mytool'...Please change it's name to something that's more 'yours' too ! e.g. 'LazerTool' and 'lazertool' ???
You need to add a
def initialize()
method, including the@pts=[]
otherwise it's zeroed every time you click by the activate method.Your last lines of code makes little sense... If you want to see the contents of the array use something INSIDE the tool's class.
onLButtonDown(1,100,100,Sketchup.active_model.active_view) pts.each{|pt|puts pt}
is wrong on so many levels
I assume you want the tool to stop and print a list of points when the user does something specific.
In thedef onLButtonDown()
method you could add a test after the@pt
is set... like@pts.each{|pt|puts pt} if @pt==[1,100,100]
Then the tool continues... which probably isn't what you want ??
Since it will be difficult for a user to pick an exact point... why not report, then exit on a new double-click method [made within the tool's class like the other tool-methods]def onLButtonDoubleClick(flags, x, y, view) UI.messagebox("Done!") ### optional ! @pts.each{|pt|puts pt} Sketchup.send_action("selectSelectionTool;") return nil end
Incidentally don't define the mod etc outside of the class, if you need them define them inside the activate() and use @mod so it's accessible in all def methods...
-
Hi TIG, I really can't thank you enough, but probably I have major issues in understanding how the program is performing.
I got the syntax onLButtonDown(flag,x,y,view) from the API, which I don't get, by the way.Why am I entering an X and a Y, if the idea behind the function is get the X and Y?
Also, I don't understand what the flag is?
And lastly, why do I need the view? do I get relative coordinates? so if I move the view I may get a duplicate point?
Sorry TIG for the hassle. -
All of the extra 'Tool' methods are automatically added by Sketchup to your code IF you add the special tool methods to your class and launch it as a 'tool'...
Read the Tool API https://developers.google.com/sketchup/docs/ourdoc/tool
These methods expect to receive various arguments like(flag,x,y,view)
[this varies with the method].
These are passed automatically by Sketchup - YOU do not do anything.
The method:
` def onLButtonDown(flag,x,y,view)do some stuff here!
end
is called if the user presses the left mouse-button - you don't need to worry about how it gets the arguments passed to it - BUT you can use them in your code... Code like...
ip1 = view.inputpoint(x,y)
@pt = ip1.positionwhich make use of the view's points clicked on screen [x/y] - with the view's '
.inputpoint' - there are several view methods you can call - see the API [https://developers.google.com/sketchup/docs/ourdoc/view](https://developers.google.com/sketchup/docs/ourdoc/view) So from that you can get the Point3d etc using an inputpoint method like '
.position`' ... -
I think I got it
thank you so much TIG.
I really appreciate your help. -
@michaelazer said:
I got the syntax onLButtonDown(flag,x,y,view) from the API, which I don't get, by the way.
... Why am I entering an X and a Y, ...Let me explain it specifically (and technically.)
All the methods listed in the API dictionary, for the Tool class, are a special kind of method, termed a "callback".
A Tool class object is an event-driven object, in this case, driven by what the user does.
When the user starts the tool, (by clicking a menu item or a toolbar button,) the SketchUp application "calls back" into the tool instance, specifically calling theactivate()
callback.YOU are expected to overrride (ie, redefine,) this callback in YOUR custom
Tool
class. (Pretending here that the APITool
class actually does or hopefully will have in the future, a true superclass; .. because it really should have one. Anyway... it helps to understand basic Ruby subclassing. Most times in Ruby your custom classes will have a superclass that they inherit methods from.)So whatever you define in the
activate()
callback runs. You are free to define normal instance methods, within your class, (often coders write areset()
method that gets called from theonCancel
callback, but you can also call it from withinactivate()
andinitialize()
, and/or whichever other method or callback you need to.)As TIG said, arguments for callbacks, are passed by the SketchUp application engine.
In event-driven code like Tool class instances, you never know which callback will get called in what order. It all depends on what the user clicks on, or what key they press. So the code does not run sequentially.
This is why you must store references in instance variables, that ALL of the instance methods and callbacks can access. It is common to reset (or init,) these variables in areset()
method.Now Ruby itself, also has callback methods that are called automatically. The most important is the private instance method
initialize()
, which is automatically called by the public class constructor methodnew()
, after it has created the new instance object. Thenew()
method passes all of the arguments IT got, into theinitialize()
callback, of the newly created instance.
!! Since this is Ruby Core stuff, you will not see it listed in the API dictionary. Please read the "guide" I wrote, click link in my signature tagline; follow the steps, and get the standard Ruby reference CHM and other resources, cheatsheets etc.But most of all.. watch out! Your about to become addicted to Ruby.
P.S.: Have you read the "Plugins/Examples/linetool.rb" ??
-
@tig said:
Incidentally don't define the
mod
etc outside of the class, if you need them, define them inside theactivate()
and use@mod
so it's accessible in all def methods...This is important, not only for access from all instance methods,...
.. but because SketchUp can (via user control,) close, open, and on the Mac, switch to another model. Each model maintains it's OWN toolstack. When your tool instance gets activated, and the
activate()
callback gets called, within it, you need to set these model specific references to the model (@mod
), it's selection or any of this model's collection objects THIS tool instance will access.SO.. this is also why TIG suggested creating a new tool instance, each time the user selects your tool from the menu or a toolbar. (Even though this is considered bad form in Ruby, ... usually best practice is to create a var to hold a reference to the instance, while it's in use, and then release it, by
@var = nil
, so Garbage Collection can dispose of the now unneeded object. BUT... it is easier, and simplier [makes more readable code, etc.] and most of all less error-prone in SketchUp embedded Ruby, if you just let each model's toolstack hold the reference to it's own instance of your tool. When the tool is no longer in use, the toolstack will no longer reference it, and Garbage Collection will clean it up automatically.)To understand why this is important... think about a user on the Mac, with 2 models open.
He is in "model one", activates your tool (which creates "instance 1" of your tool, referenced by "model one's" Tools collection (aka toolstack,)
He clicks a point and begins drawing a line...But then he remembers a line he forgot to draw in "model two", and decides to do that first, before he forgets again...
So he switches to the document window for "model two" (which causes a change in the active model,) and he then activates your line tool, for "model two"...At this point AN instance of your tool is the active tool in both model's toolstack, but "model two's" toolstack should reference "instance two" of your tool.
It is important that each model's toolstack, have it's own instance of your tool. Because the tool state (tool step, whatever you call it,) may differ between each model. At this point in time model two's tools state would be 0, as no starting point has yet been picked, but the tool instance in "model one" is at tool state 1 as the user has already picked the start point.
Your Tool class should allow the user to draw the line he forgot in "model two", then switch back to "model one" where it's OWN instance of your tool is still the active tool, and I believe the SketchUp engine will automatically call the
resume()
callback, which gives you the opportunity to redraw the view. Anyway the user should be able to continue where he was, with the first point already chosen.All this will be automatic, if you use TIG's suggestion in your menu item (or toolbar,) command blocks:
Sketchup.active_model.select_tool(Mazer::LineTool.new)
and have the references to the model, and it's collections, as instance vars created in theinitialize()
method. -
Thanks Dan, I think I actually got addicted to Ruby
If I'm not asking for too much, I have a request and a question.
Request: I theoretically understood both your comments, but can you write me a small piece of code or point me to a link to give me a practical example of how to use the resume(), activate(),new(), and initialize()? I got confused on where to put the code that I want to run when I click the Left Mouse button.
Question: I don't understand what the @mod is. Is this a variable that Sketchup understands alone or do I need to declare it?
Sorry, but after I thought I understood, I couldn't apply what I understood.
Thanks a lot -
@michaelazer said:
I don't understand what the @mod is.
It is an instance variable that YOU use within your custom class definition.
Ruby does not make you define
@name
instance variables, before use.
Usually they are initialized to some value, within a class'initialize()
method.
But a SU Tool class does not (usually,) do anything when it is initialized, only after it becomes active, and theactivate()
callback gets called. So, it's kind of take your pick.But it DOES make you define
@@name
module/class variables andNAME
constants before they are accessed.Using the word 'declare' is un-ruby, because Ruby does not really have variables, they are actually object references. (But somebody decided they would scare off old BASIC programmers if it got out that Ruby had no variables.) Anyway, do not worry about this for now, except to know that Ruby variables/references can point at any class of object when initialized, and at anytime later, can be re-assigned to point at an object of a completely different, unrelated class.
Realize that=
is the reference assignment operator, not equals (which is==
in Ruby.) -
Links... Others and I have written and sampled our fingers raw.
Check the [ Code Snippets ] indexes. It is one of the "Sticky" topics that is always at the top of the first forum page.
Then use the forum search to search on "Tool" or "Tool +activate +resume" etc.
About
new()
, don't worry about, because at this stage your not ready to override a class constructor. And you do not need to. Only know that after it does it's creation work, it callsinitialize()
in the newly created instance, passing along any arguments it itself was passed.So if you wish to pass something into your instance, via
new()
, then defineinitialize()
with arguments in it's parameter list.class Bear def initialize(myname,father) @myname = myname @father = father end end booboo = Bear.new('BooBoo','YogiBear')
And in case you are wondering, method arguments ARE local variables, whose scope is local to the method.
But these 'locals' will "go out of scope" when the method ends, and the objects they point at, will become unreferenced.
So.. we create instance variable references (whose scope is the entire instance,) that are assigned to point at those same objects; .. and after the method ends, these objects are still referenced, and will not be Garbage Collected.(ie, GC'd, as we say.)So, this:
@myname = myname
means:
The reference@myname
(shall be created if not,) and assigned to point at the object, thatmyname
is pointing at. -
Really, Dan, I can't thank you enough. I think I now have my feet on the right path.
I believe I will be a regular here on this forum. Again, many thanks to you and TIG. -
After I got your comments I worked on my program, I wrote the following code:
class MyTool # Initialization def activate @input = Sketchup;;InputPoint.new @pts[] end # Respond to left click def onLButtonDown flags, x, y, view @input.pick view, x, y sketchup;;set_status_text "Left click ", SB_VCB_LABEL pos = @input.position @pts << pos str = "%.2f, %.2f,%.2f" % [pos.x, pos.y, pos.z] Sketchup;;set_status_text str, SB_VCB_VALUE end end # Create Command object simple_cmd = UI;;Command.new("Click") { Sketchup.active_model.select_tool MyTool.new } #Add command to tools menu tool_menu = UI.menu "Tools" tool_menu.add_separator tool_menu.add_item simple_cmd
But still something is not right! I'm getting the following error:
@unknownuser said:
Error: #<NoMethodError: undefined method
<<' for nil:NilClass> C:/Program Files/Google/Google SketchUp 8/Plugins/click.rb:14:in
onLButtonDown'
C:/Program Files/Google/Google SketchUp 8/Plugins/click.rb:14Can you please help me out? Thanks!
-
It's just a couple of typos.
Line 6 should be:
@pts = []
so it is defined as an empty Array.Line 12 needs to spec the
Sketchup
module with a captial first letter. (All module and class Identifiers in Ruby are constants. Constants begin with a capital letter.)P.S.: Constants values are traditionally ALLCAPS.
And... please learn to wrap ALL your code with a toplevel module with your unique name, before you release anything.
-
Thank you so much, Dan. Your tips solved the problem.
Now, I have the program running, but I don't get the correct coordinates.
I was expecting to get some positive and some negative, according to where I am from the axes.
I clicked the 4 corners of the screen and I got the following, which doesn't look right:@unknownuser said:
x: 46 y: 48 z: 48
x: 53 y: 52 z: 46
x: 54 y: 57 z: 46
x: 52 y: 46 z: 56 -
Thank you so much, Dan. Your tips solved the problem.
Now, I have the program running, but I don't get the correct coordinates.
I was expecting to get some positive and some negative, according to where I am from the axes.
I clicked the 4 corners of the screen and I got the following, which doesn't look right:@unknownuser said:
x: 46 y: 48 z: 48 x: 53 y: 52 z: 46 x: 54 y: 57 z: 46 x: 52 y: 46 z: 56
Advertisement