Monday, June 29, 2009

Automating DXF import to QUEST

QUEST doesn't come with a BCL command for easily importing geometry, and although that would be very convenient, it's still possible to convert various CAD geometry to QUEST's PDB format.

QUEST doesn't have CAD converts baked into the QUEST.exe application file, but rather uses standalone executables located in the quest\bin folder in your QUEST installation. QUEST simply calls these executables using command line arguments to pass in information about what the user is trying to do through the QUEST UI. The converter then performs the conversion, and dumps out a temporary pdb file into your temp directory (default is C:\Tmp). QUEST has been waiting for the converter exe file to terminate, after which it just does a "retrieve" of the temp file the converter created.

So, in a nutshell, to automate this ourselves, we simply have to call the appropriate converter with the right command line arguments, and we can go around the relatively clunky QUEST UI for dealing with our CAD importing.

My use case will be generating dxf/dwg layouts from vector drawings in MS Visio, and generating a pdb file for QUEST. This means I'll be using VBA in Visio, but the same thing could easily be done using SCL (although there might be a few snags, such as making your SCL code wait until the conversion is complete)

Before I start into laying out the arguments for converting a dwg to pdb, I should mention that DELMIA/Deneb have made it a little easier on us in running these converters. They have provided some DOS batch files for setting environment variables, which the converters basically need to be sure you are a licensed QUEST user.

So to convert dwg to pdb, we use the dwg2pdb.bat and corresponding dwg2pdb.exe files. If you look at dwg2pdb.bat, you can see there's about 5 lines for telling Windows where dwg2pdb.exe is. You can get away with deleting all that junk, and finding the line that says:

%DENEB_BIN_DIR%\dwg2pdb.exe $1 ...

and replacing the %DENEB_BIN_DIR% with the full path to your QUEST installations bin dir (mine is C:\delmia\quest\bin)

The only environment variable you need to make sure is set is the LM_LICENSE_FILE, which you should have set already in your quest.bat file, and if your installation of QUEST works, you shouldn't even need to change this.

So, on to the command line arguments. To see what the arguments are, I quickly built an exe in VB6 that just echos out the command line arguments in a message box. So I renamed my dwg2pdb.exe to something else, and renamed my command line echo exe to dwg2pdb.exe. Then I went into QUEST and ran an import, at which time I was shown that there are four arguments you need to pass to run a dwg conversion:

  1. The full path to the dwg/dxf input file
  2. The full path to the output pdb file location
  3. The full path to a configuration file called dwg_cmd (more on that later)
  4. The full path to an output text callout file location
The first two arguments are fairly straightforward. You just need to get the full path to the input file, and decide where to send the output file.

The third argument is for the full path to a file called "dwg_cmd", which is automatically generated by the QUEST UI when you tell QUEST some of the parameters of the input geometry, such as the default length units, point tolerance, etc... On my system, at least, this file was stored in the quest directory. So I modified it to a default length unit of Inches, and copied the file to quest\bin. This will serve as my default dwg import profile.

The fourth and final argument was the full path to where you'd like the converter to build a plain text file containing all the text from the dwg/dxf file. You'll notice when imporing a file with text, you get the floating annotations containing your drawing text. I guess this is where that text comes from. The format is somewhat easy to understand. Basically, the first line details the number of text annotations there are. Then the annotations start. The first three lines of an annotation are the x, y, and z locations of the text callout, respectively. I'm not entirely certain what the next four lines are, though they could be rotation angles and font sizes. The next line is the number of characters in the callout, and the final line of a callout is the callout text.

Okay, so now we know all the arguments we'll need to pass to dwg2pdb.exe or .bat, to get things translated. When I tried this though, I got an error saying the executable could not find lmgr8b.dll, which is some runtime that QUEST needs in order to operate (and I'm assuming it's for the FlexLM license manager QUEST uses)

To fix this problem, I simply copied the lmgr8b.dll file from my quest dir into my quest\bin dir, and everything seemed to work. I could pass in my arguments and get a pdb file out.

So now anywhere I can generate command line arguments, I can also convert dwg/dxf files into the QUEST format automatically.

Wednesday, May 20, 2009

Selecting a file from a QUEST library (using SCL)

QUEST SCL provides a means for an SCL programmer to call the standard file selection dialog box, where the user can pick a file from a specific folder from a specific set of libraries. This function is called FILE_POP_UP. You pass in a string or string array containing the directory (or directories) to be shown in the dialog box.

The way I use FILE_POP_UP, I use the inquire_config_path to populate a string array with all the libraries containing a specified folder name, and pass that array to FILE_POP_UP. I've encapsulated this functionality into a single SCL routine you can use:

routine get_filename( folder_name : String ) : String
Var
cpaths : array [50] of String
numpths : Integer
result : String
Begin
inquire_config_path( folder_name + '$LIB',cpaths, numpths)
FILE_POP_UP(cpaths, result)
return result
End

Now rather than havign to remember how to use inquire_config_path along with FILE_POP_UP, I can simply call a routine like so:

sel_file_path = get_filename( 'DATA' )

And of course, when you want the user to be able to select an SCL logic file, you'll want to be able to provide the option of selecting from the LOGICS and SCLMACROS folders from your libraries, so I've created a routine that takes two folder names, and combines their inquire_config_path results into a single array that gets passed to the FILE_POP_UP. Here it is:

routine get_filename_multiple( folder_name1 : String ; folder_name2 : String ) : String
Var
cpaths , cpaths2 : array [50] of String
the_paths : Array[ 100 ] of String
numpths , numpths2 , i , write_idx : Integer
result : String
Begin

inquire_config_path( folder_name1 + '$LIB',cpaths, numpths)
inquire_config_path( folder_name2 + '$LIB',cpaths2, numpths2)
write_idx = 0
for i = 0 to numpths - 1 do
if( cpaths[ i ] <> '' ) then
the_paths[ write_idx ] = cpaths[ write_idx ]
write_idx = write_idx + 1
endif
endfor
for i = 0 to numpths2 - 1 do
if( cpaths2[ i ] <> '' ) then
the_paths[ write_idx ] = cpaths2[ i ]
write_idx = write_idx + 1
endif
endfor
FILE_POP_UP(the_paths, result)
return result

End

You can use it like so:

sel_file_path = get_filename_multiple( 'LOGIC' , 'SCLMACRO' )


Executing shell calls in SCL and BCL

The combination of QUEST BCL and SCL is extremely powerful not only for building and runnign simulation models, but also for providing a better experience for you to use QUEST models. BCL and SCL can allow you to greatly simplify modeling tasks, at the tradeoff of lowering QUEST's flexibility from infinite, to match with some set of assumptions you've come up with.

One powerful tool here, is the ability to generate system shell calls, and here I'll talk about this on Windows machines, only.

QUEST BCL has a call named "SYSTEM" which takes a single string argument, enclosed in single quotes. The QUEST BCL documentation gives an example of copying a file:

SYSTEM 'copy thisfile thatfile'

where thisfile is the path to the file to copy, and thatfile is the path to the file you want to copy to. This is useful enough, and pretty self explanatory, especially if you've used the Windows command line enough.

The example I'd like to provide lays in launching applications, and in this case, Microsoft Excel. To simply launch a file from the command line, all you need to do is feed the command shell the path to the file to open. Windows knows how to handle pretty much any file type you throw at it, and in the traditional GUI use of Windows you're prompted if Windows doesn't know what to do with the file.

This works fine for files where there's only one program for viewing or editing a particular file type. But what happens if you want to chose what program to use? Using the command line shell, we can specify the program file to use to open a file by passing the path of our file as an argument in starting the application. Most major applications support this.

So if you have Open Office set as the default editor of Excel files, passing an Excel file to the command shell will open it in Open Office. But if for some reason you want to open this file in Excel (without changing the default behavior) we just have to start Excel with our file as a command line argument.

So here's an SCL routine you can use to launch an Excel file:

routine launch_excel( file_name : String ) : Integer
Const
EXCEL_LOCATION 'C:/Program Files (x86)/Microsoft Office/Office12/excel.exe'
Var
bclerr : Integer
bclmsg : String
Begin

bclmsg = "SYSTEM '" + EXCEL_LOCATION + " "

bclmsg = bclmsg + file_name

bclmsg = bclmsg + " &'"
if( EXCEL_LOCATION <> '' ) then
bclerr = bcl(bclmsg)
endif
return bclerr
End

All we're doing is using the SYSTEM BCL call with the file_name argument as a commmand line argument. Pretty simple, eh?

Precompiled (always there) SCL routines

Over the course of the last few years, I have developed some SCL utility logics that I use quite often and don't like having to copy over and over again. For example, a routine for replacing strings, or converting text to upper or lower case.

It's nice to have these logics, but normally, if you wanted to use them, you'd have to find a copy of the routines declared somewhere on your computer (possibly using my subroutine indexer). However, QUEST provides a directory within your QUESTlib folder called USERDEF, with a folder for logics that, if present there, will compile every time QUEST starts up.

This means that any procedures and routines within one of those USERDEF files will be available to you without having to copy them to the logic you're working on. They're always there. If you're trying to use a precompiled routine, however, you'll have to remember to declare the routine in the Extern section of your logic file.

What I usually do to cover this is to put in the extern definition for all my routines along with the definition of the routine in the USERDEF library. This way I can just go in and grab the extern def rather than the actual SCL definition.

In fact, to make it even easier to set up the extern definitions for all my routines, I've actually written an SCL macro that will do this, more or less, for me. You can download it at:


Just load this into an SCL macro button, and when executed it will prompt you to select an SCL file. It will then search through the file you selected and export the extern representation for all your routines to a text file. It will then launch notepad and open this file for you.

Then all you have to do is copy the extern definitions. Hopefully that makes life a little easier for you.

Wednesday, May 6, 2009

Excel VBA UDFs for QUEST BCL (wow, enough acronyms?)

If you have any familiarity with Excel VBA, you've probably made at least one User Defined Function (UDF) for use in an Excel spreadsheet. If you're not familiar with it, you basically just create a function within an Excel VBA standard module that may or may not take arguments, and returns something that can be displayed in Excel. For example, here is a simple function that just returns the sum of two arguments.

Function MySum(arg1 As Variant, arg2 As Variant)
MySum = arg1 + arg2
End Function

In this function, arg1 and arg2 are of Variant types, meaning the user is able to pass in a literal number or a selected Excel cell (called a Range). By defining variables as Variants, we can assume that whatever the user passes into the function can be cast into the variable type required by the operators we are using (in this case the addition/concatentation operator +).

What some people don't necessarily think of though, is to use VBA to create string concatenating UDFs. Here's an example that just puts two pieces of text together:

Function MyConcat(arg1 As Variant, arg2 As Variant)
MyConcat = CStr(arg1) & CStr(arg2)
End Function

You can see it's similar to MySum, except that we are using the & operator which is just for string concatenation in this use (where the + operator can be addition or concatenation). Also, we are making use of the built-in VBA function CStr to cast our variant data into strings. This is because the & operator requires strings as operands.

Now to the BCL part. We can use something similar to MyConcat to make it so we can easily create BCL commands by simply passing one or more arguments into specially created UDFs. Here's an example for creating a cycle process:

Public Function create_cycle_process(process_name As Variant) As String
create_cycle_process = "CREATE CYCLE PROCESS '" & process_name & "'"
End Function

So a user only has to pass in the name of the process they want to create, and VBA returns a properly formatted BCL command to execute in QUEST. This can be pretty handy.

Even better though, is that VBA allows optional arguments in UDFs, so we can easily account for the optional arguments in many BCL commands. A good example of this is the CREATE ELEMENT CLASS command, which requires that you provide the element class type and the element class name, but allows you to also specify the number of elements and the geometry file path to use for the class display. Without optional arguments you would have to write four different UDFs to be able to build a CREATE ELEMENT CLASS command with all the possible arguments. But with VBA we can do it one. Here's how:

Public Function create_element_class(class_name, elem_type, Optional num_elem, Optional geo_name)
Dim Result
Result = "CREATE " & elem_type & " CLASS '" & class_name & "'"
If Not IsMissing(num_elem) Then
Result = Result & " NUMBER OF ELEMENTS " & CStr(num_elem)
End If
If Not IsMissing(geo_name) Then
Result = Result & " GEO '" & geo_name & "'"
End If
create_element_class = Result
End Function

This function starts by creating the base BCL command using the required arguments, class_name and elem_type. Then, it uses the VBA function IsMissing to see if our optional arguments were omitted in the function call. If not, they are used to add to the BCL command. Finally, the BCL command is returned to the user. Now the user no longer needs to remember the proper syntax for creating an element class BCL command.

To make it even easier on the user, we can provide higher level functions that provide the proper elem_type for them, so they don't have to remember those either:

Function create_mach_class(class_name , Optional num_elem , Optional geo_name )
create_mach_class = create_element_class(class_name, "MACHINE", num_elem, geo_name)
End Function

Function create_source_class(class_name , Optional num_elem , Optional geo_name )
create_source_class = create_element_class(class_name, "SOURCE", num_elem, geo_name)
End Function

Function create_sink_class(class_name , Optional num_elem , Optional geo_name )
create_sink_class = create_element_class(class_name, "SINK", num_elem, geo_name)
End Function

Function create_buffer_class(class_name , Optional num_elem , Optional geo_name )
create_buffer_class = create_element_class(class_name, "BUFFER", num_elem, geo_name)
End Function

Function create_conveyor_class(class_name , Optional num_elem , Optional geo_name )
create_conveyor_class = create_element_class(class_name, "CONVEYOR", num_elem, geo_name)
End Function

Function create_accessory_class(class_name , Optional num_elem , Optional geo_name )
create_accessory_class = create_element_class(class_name, "ACCESSORY", num_elem, geo_name)
End Function

Function create_agv_controller_class(class_name , Optional num_elem , Optional geo_name )
create_agv_controller_class = create_element_class(class_name, "AGV_CONTROLLER", num_elem, geo_name)
End Function

Function create_labor_controller_class(class_name , Optional num_elem , Optional geo_name )
create_labor_controller_class = create_element_class(class_name, "LABOR_CONTROLLER", num_elem, geo_name)
End Function

Function create_carrier_class(class_name , Optional num_elem , Optional geo_name )
create_carrier_class = create_element_class(class_name, "CARRIER", num_elem, geo_name)
End Function

Function create_agv_class(class_name , Optional num_elem , Optional geo_name )
create_agv_class = create_element_class(class_name, "AGV", num_elem, geo_name)
End Function

Function create_labor_class(class_name , Optional num_elem , Optional geo_name )
create_labor_class = create_element_class(class_name, "LABOR", num_elem, geo_name)
End Function

Function create_path_system_class(class_name , Optional num_elem , Optional geo_name )
create_path_system_class = create_element_class(class_name, "PATH SYSTEM", num_elem, geo_name)
End Function

Hopefully this has given you a good primer on using VBA UDFs to create QUEST BCL commands in Excel, so you can smarter BCL scripts that will be easier to modify and to execute in QUEST.

Friday, March 13, 2009

Wait Until Event

A lot of times in modeling a system in QUEST there comes a need for some synchronization between different elements. For instance, one model I made had a large piece removed from the main assembly part, which was stored until the main assembly part arrived at a specified machine. Once that assembly part leaves for the machine, the large part should start being delivered to the machine (while the machine is running a process to prepare the assembly part for installation of the big part).
When I first started with QUEST, I probably would have written some logic on the storage buffer holding my part to look at the machine at a specified time interval to see if the main assembly part had arrived yet. You can set the time interval to whatever you want, and there are drawbacks no matter what. If you specify a small interval (like 5 seconds), your model will slow down to a crawl because your logic is running more or less constantly. Add multiple elements running this logic and you can kiss model performance goodbye.
So what if you specify a larger interval? Again, you're still going to be running your logic a lot more than you need to, and with the larger interval you run the risk of not noticing the main assembly part arriving until too late.
There are a few ways we can address this problem without using a time interval to constantly keep checking our machine. Some would suggest using signals, where our machine would send a signal to our buffer saying the part is there, and it's time to route the part on to the machine. This will have good performance in the model, as the logic in the buffer just uses a WAIT UNTIL SIGNAL SCL command to wait.
What I don't like about this is the need to write logic for the machine to use to send the signal to the buffer. And what if we can't be sure what buffer is holding our part? Then our logic has to search for the part and alert that buffer using a signal. This is all doable, but I prefer to use (what I think is) a simpler approach: the WAIT UNTIL EVENT SCL command.
The WAIT UNTIL EVENT command would be used similar to how we would use the WAIT UNTIL SIGNAL command, except that QUEST is raising the event for us, so no logic needed anywhere except on the buffer routing the part.
To use the WAIT UNTIL EVENT command, you simply provide an event constant, and optionally a pointer to the element/element class you want to watch, as well as whether you want your logic to run right before the event, or right after. There are some other options (you can look for events on a part/part class, or segment).
So for the example above with the assembly part and the part that needs to move to the machine, our buffer would just have a loop that goes something like this:
while( true ) do
WAIT UNTIL EVENT PART_XFER FOR ELEMENT_CLASS the_elem_handle
-- look at the_elem_handle's inparts to see if our assembly part is there...
endwhile
and then our logic can continue, and route the part out of the buffer to the waiting machine. One thing to note here, too, is that to use the PART_XFER constant you must have #include at the top of your SCL file or your logics won't know what PART_XFER is and it won't compile. You could just look up the numeric codes for the event constants if you'd prefer, but the code is easier to read (especially after a year without looking at or thinking about that particular logic) and more future proof if you use the constant names.
This is just one example where we can use the WAIT UNTIL EVENT SCL command in place of using signals. Your mileage may vary, in that the events we want may not be exposed for use in the WAIT UNTIL EVENT command (for instance, when a part actually arrives at an element). In these cases you may need to use signals, but hopefully you'll be able to use WAIT UNTIL EVENT just as effectively.

Monday, March 2, 2009

How to run BCL from Excel (the simple way) Part 2

In the previous post, I showed you how to set up a batch file that launches QUEST with the path to a BCL file passed as the command line parameter.  This post will bring us the rest of the way to being able to save out a series of BCL commands generated in Excel out to a BCL file.

The easiest way to create the Excel portion of this setup is using a VBA-based Excel Addin (.xla file).  The XLA, once activated by the user, is available whenever Excel is running, and we can build toolbar buttons to execute the macros we're using.

The macros themselves are pretty straightforward if you're accustomed to writing VBA in Excel.  The VBA Project is unlocked and unprotected, so you can go in and start editing/hacking away as you'd like to.

Download the XLA file here.  Understand that it's intended just for demonstration purposes and comes with no warranty.

To install the Addin, save the XLA file to your computer and note its location.  Then start Excel and go to Tools->Add-Ins.  Select the "Browse" button and navigate to the XLA file, wherever you saved it, and that's it.  The Addin will open and you'll see a new toolbar (named BCL_Launcher_Toolbar) with a pair of buttons on it.

To use the buttons, first select the cells containing the commands you want to save/execute.  The button on the left will just save the commands to a text file, while the button on the right saves the commands and then launches QUEST and executes those commands.

The first time you use the Addin, you'll be prompted for the location of the BCL file where you want your commands saved to, as well as the location of your QUEST BCL BAT file that you created in the previous post.  After that you should be good to go.