Archive for February, 2010

Using Web Dialogs in Sketchup round two

Last post we were talking about how we can communicate between Sketchup and Web Dialogs in Sketchup. In this short tutorial we will explore how we can for example make a call from Web Dialog and open Input Box in Sketchup, where we will input our first and last name, then we will accept these values from Skatchup and display them in Web Dialog.

So let’s go to work. Open up your text editor and create a new Ruby file. Let’s create a function sendDataToJs and open up a Web Dialog:

def sendDataToJs
dialog = UI::WebDialog.new("Write your first and last name", true, "", 410, 875, 1030, 0, true)
dialog.set_url 'http://www.tdteam.com/work/engineeringtheworld/name.html'
dialog.show
end

Since we will be using inputbox in Sketchup, we have to specify what these inputboxes will show. so we create a new array called prompts = [“Type your first name”, “Type your last name”].

Now we will add action callback, where we will first show inputbox in Sketchup, then we will check if first and last names were inputed, if not we will show error msg box, else we will show inputted name in web dialog.

dialog.add_action_callback("typeName") {|dialog, action|
    input = UI.inputbox prompts, [], [], "Tell me your first and last name."
    input[0] = input[0].to_s.chomp #this holds inputted first name
    input[1] = input[1].to_s.chomp #this holds inputted last name
    if input[0] == '' || input[1] == ''
      UI.messagebox 'You have to input first and last name'
    else
      name = input[0] + ' ' + input[1]
      dialog.execute_script("dataFromSketchup('#{name}')")
    end
  }

As you can see above we have removed all whitespaces from input values, we did this with input[0] = input[0].to_s.chomp and input[1] = input[1].to_s.chomp. If first and last name were inputted (and do not consist only of whitespaces) we assign first and last name to variable name, and call function dataFromSketchup(name) in our Web Dialog.

I also added a action call back to show dialog when we click a link show Console in Web Dialog.

The complete script is:

def sendDataToJs
dialog = UI::WebDialog.new("Write your first and last name", true, "", 410, 875, 1030, 0, true)
dialog.set_url 'http://www.tdteam.com/work/engineeringtheworld/name.html'
dialog.show

  prompts = ["Type your first name", "Type your last name"]
  
  dialog.add_action_callback("typeName") {|dialog, action|
    input = UI.inputbox prompts, [], [], "Tell me your first and last name."
    input[0] = input[0].to_s.chomp
    input[1] = input[1].to_s.chomp
    if input[0] == '' || input[1] == ''
      UI.messagebox 'You have to input first and last name'
    else
      name = input[0] + ' ' + input[1]
      dialog.execute_script("dataFromSketchup('#{name}')")
    end
  }
  #show console call up
  dialog.add_action_callback("showConsole") {|dialog, action| Sketchup.send_action("showRubyPanel:")}
end

Now let’s take a look at our HTML file name.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Get your name from Sketchup</title>
<script language="javascript" src="js/prototype.js"></script>
<script language="javascript">
function dataFromSketchup(name){
   $('nameId').update('Your name is <strong>' + name + '<strong>');
}
</script>
</head>
<body>
<div id="nameId">

</div>
<a href="skp:typeName@true">Type in your name in Sketchup</a><br />
<a href="skp:showConsole@true">Show Console</a>
</body>
</html>

I have used javascript framework Prototype in this example, because I do not want to think about browser compatibility when doing JavaScript, that is why I suggest you to use a JavaScript framework like: Prototype or jQuery.

Code here in name.html is pretty straightforward so there is not much to talk about. As you can see we have to links, one is calling typeName and other one showConsole from our Ruby code. We also added a function dataFromSketchup that accepts one argument (which is the name inputted).
Then we just use $ selector to find tag with nameId id and use update Prototype method to show inputted name inside div tag with id=nameId. Name will be show as bold text.

, ,

Leave a comment

Creating Web Dialogs in Sketchup and making calls from and to Sketchup

Ok, in last three little tutorials I showed some of the things you can do with just a little Ruby code in Sketchup with it’s Ruby API.

Now it is time to move to as far as I am concerned the most interesting part of doing Sketchup development and that is using Web Dialogs. Since I have been doing web related projects for years now I feel right at home using web pages as means to do something. Sketchup let’s us open Web Dialog, which is nothing more than a normal browser window that is opened within Sketchup, where you can make calls from Sketchup to web page on other side and vice versa (via JavaScript).

One thing to note is that webpage is opened in a default browser in a given operating system, that being Internet Explorer on Windows and Safari on a Mac.

Let’s first take a look at how we can open a Web Dialog within Sketchup using Ruby call:

dialog = UI::WebDialog.new("Title of Web Dialog", true, "", 410, 875, 1030, 0, true)

As you can see there are 8 arguments that Web Dialog accepts when it is being created.

  1. title – Web Dialog title
  2. scrolable – true if you want page to be scrollable and false if you don’t
  3. pref key – The registry entry where the location and size of the dialog will be saved
  4. width – width of Web Dialog
  5. height – height of Web Dialog
  6. left – position from the left of the screen in pixels
  7. top – postion from the top of the screen in pixles
  8. resizable – true if you want the webdialog to be resizable, false if you do not want the webdialog to be resizable

Next thing we want to do is show assign which web page to open as our Web Dialog, we do this by: dialog.set_url filepath, where filepath is the path to the html document we want to open. The last thing to do is to show our Web Dialog, which we do by: dialog.show.

Second part of what I wanted to talk in this post is how can we communicate between our Web Dialog and Sketchup.

First let’s see how we can use Ruby to execute any JavaScript code in Web Dialog. If we stored our Web Dialog in a variable called dialog, we can do the following:

dialog.execute_script("any JavaScript here will be executed in Web Dialog");
//we could show JavaScript alert with
dialog.execute_script("alert('Hello from Web Dialog')");

The second line of code will produce a alert window from Web Dialog with text Hello from Web Dialog.

If we wanted to call let’s say a function called test_function, we would just say dialog.execute_script(“test_function()”);, and test_function in Web Dialog will be executed.

One thing to note to all Mac users is that you have to upgrade Sketchup to 6.0 in order for execute_script to work, there was a bug that prevented scripts to be called from Ruby in Web Dialog and that has been fixed in Sketchup 7.

Ok, now to the other side, how to execute code in Ruby (Sketchup) from Web Dialog. This has to be done both on Ruby side and Web Dialog side. Let’s suppose we have a function defined in Ruby called show_hello_world defined like:

def show_hello_world
  UI.messagebox('Hello world');
end

Now we have to tell Ruby to be expecting a call from JavaScript, we can do this by calling add_action_callback method on Web Dialog. Let’s see an example:

dialog.add_action_callback("execRubyFunction") {|dialog, action| show_hello_world}

All there is left to do is to add a link to Web Dialog that will execute show_hello_world function:

<a href="skp:execRubyFunction@true">Execute show_hello_world </a>

As you look at the code above, you can see that we have to add name to add_action_callback within brackets, in our case we wrote execRubyFunction, this is the name we have to repeat in a link, so we do skp:execRubyFunction for our link (plus we add @true).

So if we wrote dialog.add_action_callback(“someOtherNameHere”)… we would have to do the same in html tag skp:someOtherNameHere@true for this to work.

One thing to note also is that after we call dialog.add_action_callback(“nameFunction”){|dialog, action|, everything after this and befor closing } is the code that will be executed when this code is executed from Web Dialog.

dialog.add_action_callback("nameFunction"){|dialog, action|
puts 'this will be written to console'
puts 'this too'
puts 'and this too'
}

Ok, at the end let’s to one quick example, we will use our knowledge of Web Dialogs and action_callback’s to show console of Sketchup when clicking on a link in Web Dialog.

Here is the code for showConsole.rb:

require 'sketchup.rb'
dialog = UI::WebDialog.new("Sending data to JavaScript", true, "", 410, 875, 1030, 0, true)
dialog.set_url 'http://www.tdteam.com/work/engineeringtheworld/showConsole.html'
dialog.show
dialog.add_action_callback("showConsole"){|dialog, action|
  Sketchup.send_action("showRubyPanel:")
}

And sourcecode for showConsole.html (you can use the same address as the one above and you will get this HTML file – also make sure to set width, height, top and left settings when executing WebDialog.new to values of your liking)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Show console</title>
</head>

<body>
<a href="skp:showConsole@true">Show console</a>
</body>
</html>

When you try out this code in Sketchup using Web Console you show see that console will be shown, when you click on a link in Web Dialog. This is because we execute Sketchup.send_action(“showRubyPanel:”) when link is called. You can see what else you can call by using Sketchup.send_action on this link: http://code.google.com/apis/sketchup/docs/ourdoc/sketchup.html#send_action)

, , , , ,

Leave a comment

Adding different types of entities to different layers

Building upon our previous example where we counted number of different types of entities in Sketchup file, we will take a look at how we can create new layers in Sketchup with Ruby API and assign different entity types to their appropriate layer.

We will create 3 layers of most basic types of entities in Sketchup, and those are Edge, Face and ComponentInstance.

Let’s open up a new file in text editor and define a function assign_layer_for_elements. First we will create 3 layers: edges, faces, componentInstances.

  model = Sketchup.active_model
  ent = model.entities
  #create layers for each type of entity
  edge_layer = model.layers.add("edges")
  face_layer = model.layers.add("faces")
  componentInstance_layer = model.layers.add("componentInstances")

As you can see from the code above, we can create layers current active model by model.layer.add(“name of layer”). We did this 3 times for 3 types of entities.

Next we will once again use each method on ent variable (which holds all entities in our Sketchup model) and based upon their type assign them to appropriate layer.

ent.each{|x|
   x.layer = edge_layer if x.is_a? Sketchup::Edge
   x.layer = face_layer if x.is_a? Sketchup::Face
   x.layer = componentInstance_layer if x.is_a? Sketchup::ComponentInstance
  }

If current entity (x) is of type Sketchup:Edge, we assign it to edge_layer, the same goes for Sketchup::Face and Sketchup::ComponentInstance.

Run this code in Web Console and you will see that 3 new layers appeared (if you don’t have layers view active, go to Window->Layers). In my model, if I turn off all layers except edges layer I get the following result (as compared to view if I have all layers turned ON):

Final sourcecode of assign_layer_for_elements function

def assign_layer_for_elements
  model = Sketchup.active_model
  ent = model.entities
  #create layers for each type of element
  edge_layer = model.layers.add("edges")
  face_layer = model.layers.add("faces")
  componentInstance_layer = model.layers.add("componentInstances")
  
  ent.each{|x|
   x.layer = edge_layer if x.is_a? Sketchup::Edge
   x.layer = face_layer if x.is_a? Sketchup::Face
   x.layer = componentInstance_layer if x.is_a? Sketchup::ComponentInstance
  }

end

, ,

Leave a comment

Counting types of entities in Sketchup with Ruby API

Following up on the previous example, where I showed how you can get an array of all entities and an array of all selected entities we will sort the entities by type or class this time, and let the user know how many entities of each type there are.

We will once again begin our script with require ‘sketchup.rb’.

We will check and count the following types of entities:

  • Sketchup::Edge
  • Sketchup::Face
  • Sketchup::ConstructionLine
  • Sketchup::ComponentInstance
  • Sketchup::ConstructionPoint
  • Sketchup::Image

Let’s create a function sort_count_elements. Again we will asign active model to variable model and all model entities to variable ent. We will also set variables face, edge, componentInstance, constructionPoint, constructionLine and image to equal 0.

Now we will go through the ent array and if it is going to be of type Sketchup:Edge we will add 1 to edge varable, and the same for face, constructionLine etc.

At the end we will notify user about how many entities of each type we counted.

Again save the code and load it in Sketchup with Web Colsole plugin and press Eval (make sure you add sort_count_elements call as the last line in Web Console).

def sort_count_elements
  model = Sketchup.active_model
  ent = model.entities
  edge = face = constructionLine = componentInstance = constructionPoint = image = 0
  ent.each {|x| 
   edge+= 1 if x.is_a? Sketchup::Edge
   face+= 1 if x.is_a? Sketchup::Face
   constructionLine+= 1 if x.is_a? Sketchup::ConstructionLine
   componentInstance+= 1 if x.is_a? Sketchup::ComponentInstance
   constructionPoint+= 1 if x.is_a? Sketchup::ConstructionPoint
   image+= 1 if x.is_a? Sketchup::Image
  }
  
  UI.messagebox "Nr edges: #{edge}\nNr Faces: #{face}\nNr constructionLine: #{constructionLine}\nNr componentInstance: #{componentInstance}\nNr constructionPoint: #{constructionPoint}\nNr images: #{image}"

end

The end result will show you a pop up like the one below:

As you look at the code above you will see that we are going through array ent with ent.each. each is a standard iterator for arrays, ranges and hashes. ent.each method call is followed by code within a block that will be executed for every element in array, range or hash. In our example we used:

ent.each {|x| do something with x}, where x is value of the current element in array (in our case an entity). This is where we check if entity is of type Face, Edge etc., which we do with x.is_a? Sketchup::Face for example. We could also use x.class == Sketchup::Face, but using is_a? is nicer since it is a method made for comparing if given class is the same as the one provided.

, ,

Leave a comment

Google Sketchup Ruby API

Since I am doing some work in Google Sketchup these days using it’s Ruby API, I would like to talk about it a little bit here.

Google Sketchup is a powerful tool that Google acquired couple of years back. Idea behind Sketchup is, that you can learn how to do some pretty interesting 3D modeling in a short period of time. There are a lot of 3D modeling software out there, but probably Sketchup is the one that is the easiest to learn, but at the same time pretty powerful.

In order for users to make their own plugins or extenitions Sketchup provides us with Ruby Sketchup API. As you know API stands for Application Programming Interface, which means that you are given a set of tools or calls that you can make to interact with Sketchup model with Ruby script.

Today we will take a look at a fairly easy Ruby program, which will show us how many elements there are in a model, and how many of them are currently selected.

Before we go into any code I would like you to download a plugin called Web Console made by Jim Foltz. It can be downloaded from this URL: http://sketchuptips.blogspot.com/2007/08/plugin-webconsolerb.html.

What Web Console allows us to do is input or load a Ruby script into Skecthup and try it out. Normaly without this plugin, we would have to restart Sketchup every time we made a change to our plugin code to retry it.

So after you installed Web Console plugin you are ready to follow this tutorial. (plugins are saved in plugins folder inside your Google Sketchup application folder).

Now create a new file in your favorite text editor. I am using the free Komodo Edit.

In the first line write the following:

require 'sketchup.rb'

This will always be the first line of any plugin we make for Sketchup. It just ads contents of sketchup.rb file into this file so we can start interacting with Sketchup.

Next we will define a function count_elements, inside this function we will get all elements in our Sketchup model (or entities as they are called in Sketchup), and all selected elements or entities for our model. We will then notify the user about how many entities there are and how many of them are selected.

def count_elements
  model = Sketchup.active_model
  ent = model.entities
  selection = model.selection
 
  UI.messagebox "Number of elements in this model: #{ent.length} \nNumber of selected elements: #{selection.length}"

end

Let’s go through this code line by line…since it only has 4 lines of code it will be easy 🙂

First we save active model (that is the model that we currently have on screen) into a variable called model. Now we can store all entities of active model into an array ent (with model.entities). Next we store the selected entities into variable selection.

At the end we want to present a pop up window that will state how many elements there are in our model and how many of them are selected.

Pop ups can be created by UI.messagebox “message we want to show in our pop up”.

Since we are using double quotes and not single ones we can include variables by using construct #{variable here}. We can add new line with \n.

Now save this script with rb extension. You can save it anywhere, since we will activate it with Web Console plugin.

Open Sketchup, either load any 3D model you have or just add some shapes, lines and such and select couple of them. Now open Web Console, click load button at the top of the plugin window, load script you just saved. Go to the script and add one line calling our count_elements function and press Eval. Your Web Console should look like the one below:

The UI message box shown on my loaded model can be seen below:

, , ,

Leave a comment

Added support for SS connections and lag

Program for project scheduling made in Flex

OnTime program for project scheduling is located on this URL: http://www.tdteam.com/ontime/

In couple of previous posts, I was talking about a program I made in Flex for project scheduling and financial analysis of any kind of building project.

This week I added two more things to the program about which I want to talk about in this post. There were 2 connections possible when connecting two tasks together. First one was FS – Finish Start and other one was FF – Finish Finish. Now program also supports SS connection – Start Start.

The other thing that was added is the ability to add lag with connections, so for example if we have 3 tasks: Task 1, Task 2 and Task 3, we can do the following:

  • connect Task 2 with Task 1, so that Task 2 will start 3 days after Task 1 is started, we do this by: 1 SS (3)
  • connect Task 3 and Task 2, so that Task 3 starts the same day as Task 2, we do this by: 2 SS

As you can see from the short example above it is optional to put lag into any connection, if however we want to add it, we have to specify it within brackets, where the number between the brackets will be set as lag in days (so connection 2 (8) will mean we want to connect current task with task with id 2, with connection FS (default connection) and lag of 8 days).

If anyone has any comments about the program, or what you miss in it, please let me know 🙂

, ,

Leave a comment

Select and deselect checkboxes in a dataGrid in Flex

While reading forum http://www.flexdeveloper.eu/forums/mxml/ some one wanted to know how to make the following functionality in Flex.

For each row in a dataGrid we have two checkboxes (in his example one is compulsory and the other optional). What we want is that all the time only one checkbox is checked, so if I check optional to true, I want the other one (compulsory) to be false.

This can be done with an itemRenderer that will function also as our itemEditor. Here is the code of the app:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" initialize="init()">
<mx:Script>
	<![CDATA[
		import mx.collections.ArrayCollection;
		[Bindable] public var ac:ArrayCollection = new ArrayCollection;
		public function init():void{
			ac.addItem({id: 1, compulsory: true, optional:false});
			ac.addItem({id: 2, compulsory: false, optional:true});
			ac.addItem({id: 3, compulsory: true, optional:false});
		}
	]]>
</mx:Script>
	<mx:DataGrid dataProvider="{ac}" id="DG">
		<mx:columns>
			<mx:DataGridColumn headerText="ID" dataField="id"/>
			<mx:DataGridColumn headerText="Compulsory" itemRenderer="CheckBoxComponent" 
				editable="true" rendererIsEditor="true" dataField="compulsory"/>
			<mx:DataGridColumn headerText="Optional" itemRenderer="CheckBoxComponent" 
				editable="true" rendererIsEditor="true" dataField="optional"/>
		</mx:columns>
	</mx:DataGrid>
</mx:Application>

So we have an initialize function set to init(), in there we just add arrayCollection ac to dataGrid with id DG as it’s dataprovider. In the DataGrid itself we have 3 columns (id, compulsory and optional), since our itemRenderer will also be our editor of data we set rendererIsEditor=”true”.

Now let’s take a look at the code that is used as our itemRenderer.

<?xml version="1.0" encoding="utf-8"?>
<mx:CheckBox xmlns:mx="http://www.adobe.com/2006/mxml" change="change()">
	<mx:Script>
		<![CDATA[
		import mx.controls.Alert;
			override public function set data( value:Object ) : void {
				super.data = value;
			}
			private function change():void{
				if (data.optional){
					data.optional = false;
					data.compulsory = true;
				} else {
					data.optional = true;
					data.compulsory = false;
				}
				document.ac.refresh();
			}
		]]>
	</mx:Script>
</mx:CheckBox>

Everything here is very straightforward, we first have to get data for the clicked row, we do this by overriding set data function, and in it calling super.data = value.

As this component is based on CheckBox component we add a function to be called when CheckBox is changed (change=”change()”).

Since we know that if we know that every time one checkbox is checked other one has to be unchecked and vice versa.

Inside change() function we check if optional is true, and if it is we set optional to false and compulsory to true, if optional is false we set it to true and compulsory to false.

At the end we have to ensure that dataGrid will show the data we changed so we have to call document.ac.refresh() function, which just says to the arraycollection to refresh it self (along with our changed data).

You can try the app right here: http://www.tdteam.com/work/engineeringtheworld/example.swf

, ,

Leave a comment