Interest in a Networking Sockets Workaround
-
For the Sketchup side you'll need a message queue and an Observer.
Here's a preliminary example that seems to work.
It uses an AttributeDictionary as the queue, and an EntityObserver (since that's the superclass of the AttributeDictionary class.)ISSUES:
(1) This approach will likely need an AppObserver and ModelObserver as well, to be sure that the 'SocketQueue' dictionary is removed from the model before saving, and when Sketchup closes.
(2) May need modifiction to work on Mac with mutilple models open.
-
A word about the queue keys.
They will need to be unique and/or oridinal. Perhaps a timestamp string?
-
Thanks guys. I appreciate the help you are willing to give:).
My motivation for doing this network bridge was because I wanted to pursue a larger project that required network comms.I just recently became interested in actually doing that original project again so I think I will pick this up and run with it
-
@richmorin said:
Just wanted to note my interest in this effort. I'd love to have a reliable way to send and receive data on sockets.
However, I'm curious why you're writing the server in C++. Is there some reason why it couldn't be written in Ruby?
Sorry I never answered this. I believe you've probably figured it out by now but Sketchup doesn't support sockets. Not in an official sense and not very well even in the hacked up unofficial sense:)...or at least...last time I looked (long time ago)
-
Strange that for all the interest, noone at all took a look at the code example in my previous post.
-
Hey Dan,
Tried to load it up...Don't know how to get it to work.Running latest free Sketchup version and installed RDE etc. I think I execute the script correctly via your isntructions (copied all files into plugin dir, start sketch, start RDE, register Script, run code)but nothing valueable happens. I hit "run" after I type Sketchup.version into RDE....nothing useful, just error. Could use a little help if you really are up for getting your Ruby Bridge out there.
I'm using Sketchup 8 on Windows 7.
Your example ruby code for the Dictspy.rb doesn't do any good without the bridge part.
My Bridge was specifically networking...but if you get the whole shebang then I can just build ontop. Failing any further input from you I can keep working on my own interface which would be just the networking sockets.
-
It seems that Sketchup is running the bridge_load script as I can get UI.messagebox statements that I put into it.
I even get the "SketchUp Bridge already started", if I force RDE to run the macro via Macro->SUB. So it seems like something is getting executed.
But when I go to use Sketchup.version in RDE I get C:/Users/RABIDC~1/AppData/Local/Temp/rb4126.tmp:1:in `<main>': uninitialized constant Object::Sketchup (NameError)
Which makes me think it's not being recognized.I know I could have all sorts of configuration problems so let me know what you think. Ruby 192, windows 7, Sketchup 8.
-
@rabidcicada said:
Hey Dan,
Your example ruby code for the Dictspy.rb doesn't do any good without the bridge part.Of course not! (I was showing what needs to be done on the Ruby-side.)
YOUR bridge needs to write "transactions" (which will be attributes consisting of a key [a unique timestamp is fine,] and a value which would be your data [as described in the first post,]) to the attribute dictionary of a special name.
I chose the dictionary name 'SocketQueue' for the example.
On the C++ side, you'll need to get the handle to the current model, and write your messages to the attribute dictionary named 'SocketQueue', using it's set_attribute() method.
You don't have a choice. The only way to have Sketchup perform an event-driven task, (because Sketchup "owns" the event loop,) is to use one of the Ruby API's observer classes that reacts to changes in a list-like object. The only candidate is the the AttributeDictionary class for the queue (which is a subclass of Entity,) and a custom EntityObserver subclass.
Then to process messages you (or whomever uses the bridge,) needs to replace the comment:
# This is where the message processing conditional statement goes.
(within the process_message method,) with some kind of conditional statement block(s) (if or case, etc.) that processes the messages.In practice it may be easier if coders create a subclass of the DictSpy, and override the process_message() method. The name DictSpy could be changed to QueueSpy, SocketQueueSpy, etc. whatever you wish.
You can get fancy and add a module method that dettachs the observer to stop processing, and another to attach it again. Perhap some queue methods to clear the queue, etc.
-
@rabidcicada said:
I know I could have all sorts of configuration problems so let me know what you think. Ruby 192, windows 7, Sketchup 8.
Sketchup 8 loads Ruby v1.8.6-p287
Sketchup cannot load Ruby in the 1.9.x branch (as far as I know, unless you found some trick?)RDE could be the problem... try testing your code using Alex's WebConsole/Editor.
-
RabidCicada how do you run the code in RDE ? from that error it looks that you dont use sub.exe to send the code from RDE to SketchUp via SketchUp Bridge
-
Thanks for the responses.
I am SEVERELY unfamiliar with hacking up sketchup...FYI.
Question:
Does Sketchup have it's own local copy of Ruby 1.8.6 etc? Or are you saying that I must use that version if I am to successfuly drive sketchup externally?I run code through RDE by simply typing it into the main editing window and then selecting "run" from the menu. Is that not the right way?
I've already registered the SUB script with RDE.
I may not be "executing" ruby code the right way. I start Sketchup with the scripts and exe in the plugins directory. I believe bridge_load.rb runs because I get a messagebox notification I put in there on purpose (for the success case). Then I start RDE. I run SUB script via toolbar->macro->SUB (I get the error about only one instance being allowed). Then I type some code into the normal box in RDE. Then I hit "run" and I get the error.Am I supposed to do things differently?
I would appreciate if you laid out an exact step by step example to successfully execute a "Hello World" in a messagebox from the RDE interface.
-
Should I separately install Ruby 1.8.6?
-
Thanks for the pointer to Alexs stuff...I'll play with that also.
-
Oh...Wait a minute....
I'm at work so I can't test this but.....
Are you supposed to (For each and every piece of Ruby code you want to execute):
(1)Put your code in normal RDE console
(2)Execute SUB script via Toolbar->Macro->SUBI assumed that the bridge was a constant bridge and that you just execute it once....then input and "run" ruby code as normal in RDE.
-
@unknownuser said:
(1)Put your code in normal RDE console
(2)Execute SUB script via Toolbar->Macro->SUByup, that is the workflow to use SketchUp Bridge. there is no need to install separately Ruby 1.8.6, but it helps in developing (checking source, running quick tests outside Sketchup in an automated way, ...)
-
Thanks TBD.
I just didn't pay close enough attention to your instructions...which clearly state what to do.
-
back to the original post... since there's seems to be some interest in networking sockets for sketchup. I'll tell you my findings so far...
While, I would also be interested in a C++ networking bridge, I have had success using ruby sockets in sketchup 8 on windows 7. This might be easier for people who only know ruby. Ruby sockect class comes from the socket.so file found in the 1.8.6 full ruby install. Once you have that file, just require it in your code:
require 'socket'
as you can probably guess I copied the socket.so file to the /plugins directory. That seems to work fine.
For my example there are two ruby apps. One will run in the ruby console (outside of sketchup) I'll call it the "server" and the other is sketchup itself, I'll call that the "client".
Next, I initialize a UDPsocket object and bind it to an ip and port. For my purposes I am using UDP instead of TCP because I need to send lots of little messages very often and I don't care if one gets lost. If you needed to do TCP with ruby, it can do that too.
socket = UDPSocket.new socket.bind(ip, port)
"ip" is your localhost ip address, "localhost" will work if it's just local communication. and "port" can be whatever port you want as long as it's available. I use 2000 for the server app and I use 0 for the client which causes it to generate a dynamic port number.
On the server side, I setup a loop to check for incoming messages, send any new messages, and repeat:
while connection == true begin message = socket.recvfrom_nonblock(maxlen) # messages look like this; # ["message string", ["AF_INET", 4913, "localhost", "127.0.0.1"]] # the second item of the array is the address information of the sender client_ip = message[1][3] client_port = message[1][1] new_message = true rescue Errno;;EWOULDBLOCK # fires if there is no new message new_message = false rescue Errno;;ECONNRESET # fires if something goes wrong on the client side. new_message = false connection = false break end if new_message == true # do something here with the incoming message and send a reply socket.send("got it, thanks.", 0, client_ip, client_port) end end
As you can see, I had to do some error catching on the receive. This is the only way I've figured out how to get non-blocking to work. I also left out the "do something" part to keep it simple.
On the client/Sketchup side, I make a similar loop but this time I have to use a UI.start_timer instead of a while statement because I need to give control back to sketchup each loop so that sketchup can keep doing it's thing:
timer_id = UI.start_timer(0.1, true) { begin message = socket.recvfrom_nonblock(maxlen) new_message = true rescue Errno;;EWOULDBLOCK # fires if there is no new message new_message = false rescue Errno;;ECONNRESET # fires if something goes wrong on the server side. new_message = false UI.stop_timer @timer_id break end if new_message == true # do something here with the incoming message end if outgoing_message != nil socket.send(outgoing message, 0, server_ip, server_port) end }
Couple notes here. One, I have the timer set to 1/10th of a second. Haven't really tested other speeds but this seems to work pretty good. Two, "outgoing_message" for me is something created by observers. Three, I just hard code in the server_ip, and server_port("127.0.0.1", 2000). this won't change in my application.
In this example I am sending useful information to the server, not the other way. you can reverse it or go both ways. I have also left out any acknowledgment systems you might need to confirm the other side got what you sent. I believe TCP has this built in. UDP over the internet is unreliable but if it's just meant to be local, the packet gets there pretty much every time.
Anyway, just thought I would share. I'm sure there are probably some benefits to a C++ solution but I only know ruby so I'm stuck working with what I've got. If anyone looks at this and thinks this it completely wrong, let me know. I'm still learning.
-Mike
-
One last question....Did you figure out the method of ruby integration through some documentation? or did you simply poke around enough?
I guess if you are already familiar with he inner working of Ruby at the object file level then you would already recognize some stuff you'd find in Sketchups directories.
I was learning the sketchup API as a reason to learn Ruby >.<.
Anyways...thanks a lot for the treasure trove of information you've just presented me...now to do something useful.
-
@rabidcicada said:
One last question....Did you figure out the method of ruby integration through some documentation? or did you simply poke around enough?
The old "Pick-Axe" book has a chapter on how applications embed Ruby as a scripting extension. See the chapter Extending Ruby, and scroll down to the section "Embedding a Ruby Interpreter".
However, that example seems to use a standard Ruby install... Sketchup did a few custom things.. so yes a bit of poking around, trial and error, was needed to understand.@rabidcicada said:
I guess if you are already familiar with the inner working of Ruby at the object file level then you would already recognize some stuff you'd find in Sketchups directories.
Not really. Most people who learn standard Ruby first, are not familar with a Ruby implemented for embedding in applications. They expect the Ruby dirs and files to be in the standard locations.
And then Sketchup distro's with only the Core classes, and none of the standard fare of extended libraries.@rabidcicada said:
I was learning the sketchup API as a reason to learn Ruby
Same Same. Sketchup introduced me to Ruby.
-
@dan rathbun said:
You don't have a choice. The only way to have Sketchup perform an event-driven task, (because Sketchup "owns" the event loop,) is to use one of the Ruby API's observer classes that reacts to changes in a list-like object.
I stand corrected. I was thinking event-driven.
mtalbott, showed a polling means using the UI.start_timer block. Forget about that... should be much eaiser to implement.
Advertisement