Tuesday, September 1, 2009

Examples of using C_EXEC in SCL

SCL provides a function called C_EXEC which will allow you to execute functions in Windows DLL files. If you have used the Declare Function ... functionality in VBA then you'll see this is the same kind of functionality.

Basically, to use C_EXEC you need to know the name of the function in the DLL file, as well as its argument list. For example, the Sleep function resides in the kernel32.dll file that comes with Windows. It takes one argument, which is the sleep time in milliseconds. This function simply delays for the specified period of time, then returns control of the program back to whatever called it. This allows you to put a pause of whatever length you want in your program.

Since there is no built-in sleep function in SCL, this is a good function to have handy, always compiled and ready to use.

The syntax for the C_EXEC function is simple:

C_EXEC( routine_name_and_location , arg1 , arg...)

where routine_name_and_location is the dll filename and function name separated by a colon (:). Any extra arguments to C_EXEC should correspond to arguments in the DLL function.

So for the Sleep function, a C_EXEC call would look like this:
C_EXEC( 'kernel32.dll:Sleep' , 1000 )

Calling this in SCL would make your SCL code delay for 1 second.

We can wrap this in a nice routine that you can leave compiled all the time:

routine sleep( milliseconds : Integer ) : Integer
Const
THE_DLL 'kernel32.dll'
THE_CMD 'Sleep'
Var
func_return : Integer
Begin
/*
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
*/
func_return = c_exec( THE_DLL + ":" + THE_CMD , \
milliseconds )
return true
End


Here's another example using the mciSendStringA function in winmm.dll to open your CD drive door (I know, very useful):

procedure open_cd_drive()
Const
THE_DLL 'winmm.dll'
THE_CMD 'mciSendStringA'
Var
func_return : Integer
lpstrCommand : String
lpstrReturnString : String
uReturnLength : Integer
hwndCallback : Integer
Begin

/*
Public Declare Function SendCDcmd Lib "winmm.dll" \
Alias "mciSendStringA" ( \
ByVal lpstrCommand As String, \
ByVal lpstrReturnString As String, \
ByVal uReturnLength As Long, \
ByVal hwndCallback As Long) As Long
*/
lpstrCommand = 'set CDAudio door open'
lpstrReturnString = ''
uReturnLength = 127
hwndCallback = 0

func_return = c_exec( THE_DLL + ":" + THE_CMD , \
lpstrCommand , \
lpstrReturnString , \
uReturnLength , \
hwndCallback )

End

Hopefully this gives you a good taste of what is possible with using C_EXEC to extend some of the functionality of SCL beyond what's currently available. If you want to see how you can create your own Windows DLL (and thereby provide your own custom functions in a compiled form) you can look at this web site. At some point in the future I hope to release a DLL library of functions to help add some functionality to SCL.

Monday, August 31, 2009

Converting SketchUp models to QUEST pdb files

SketchUp is a 3D modeling tool which was originally developed by @Last Software, which was purchased by Google. Google now provides the SketchUp tool for free, to encourage modelers to create real-life geometry for its Google Earth tool, as well as populating their 3D Warehouse, which is a huge repository of 3D models.

SketchUp is very easy to use, easy to learn, and very powerful for being a free tool. It's much easier to use than the QUEST CAD world, though the free version of SketchUp has some limitations which make it difficult to get SketchUp models into a format that QUEST will recognize. The free version will only export to the Collada format, which is not supported by QUEST. This means you have to use some intermediary program that can read Collada files and export to something QUEST understands (i.e. VRML 1.0, DXF, OpenFlight Obj).

Until now, the easiest way to get from SketchUp into QUEST was to shell out $495 for a license of SketchUp Pro, which includes a DXF exporter.

I say until now, because luckily the Guitar-List.com website has put together a Ruby plugin for SketchUp that exports from SketchUp to DXF, for free.

That's only half the battle, so with permission from Guitar-List.com I was able to modify their script to use QUEST's built-in DXF to PDB conversion tool so you can automatically export from QUEST to DXF to PDB.

You can download the ruby script here. Download it to your Program Files/Google/Google Sketchup/Plugins folder.

One pre-requisite for this script to work, however, is that you must have your dwg2pdb.bat file configured so that DENEB_PATH is set to whatever your Deneb or Delmia path is. You also must set the DENEB_PROD_DIR to point to the quest folder (default was vmap for me) and make sure LM_LICENSE_FILE is pointing to the right place. These settings should more or less mimic some of the settings from your quest.bat file.

Next copy the dwg_cmd file from your quest folder to the bin folder, and set your default units to inches.

The last thing you'll have to do is edit line 64 of the skp_to_pdb.rb file to point to your dwg2pdb.bat and dwg_cmd files.

It may take a bit of work to get set up, but I've been using it for a few weeks and it's nice to be able to use a modern 3D drawing tool and easily load the geometry in QUEST.

A few notes on usage, make sure you draw to the proper scale in SketchUp, and be mindful of the origin. If your geometry is far from the origin in SketchUp it'll be far from the origin in QUEST, too.

Again if you want to contact me directly just hit me up here or leave a comment.

Tuesday, August 25, 2009

How to run QUEST from Excel VBA and wait for QUEST to finish

This post is not meant to show you how you can use VBA to run BCL commands in QUEST. It's just a small tweak to how we run QUEST in this previous post. In that previous VBA solution, I used the baked-in Shell command to run a QUEST.bat file in BCL mode.

One reason I can think of for using this functionality is being able to automatically run some experimentation in QUEST, without having to mess with sockets (until we can get an easy to use library for QUEST sockets). This way we can have some Excel worksheet with accompanying VBA code that will run QUEST, wait for QUEST to exit, then check some output file from QUEST before advancing in the experiment. I am working on a project where this is pretty much exactly what I'll be doing.

To make this work, we just need to use a different Shell command, that will effectively halt execution of our VBA code while QUEST runs, and resume execution once the QUEST process we started ends.

Basically, we will re-use the code found here to replace our basic Shell call. All the linked code does is start QUEST using the Shell command, and uses a few Windows API calls to monitor the QUEST.exe process we started, looping until that process ends. Once the process ends, we exit the loop and VBA continues executing code.

I've updated the QUEST_BCL_Addin.xla file which can be downloaded here to include this new ShellAndWait functionality. If you call the SaveABCL function with LaunchQUEST = True and TransferToMenu = False then your code will wait for QUEST to exit before resuming.

I'd like to start adding some QUEST BCL User Defined Functions to this addin in the near future. I have some written already, but would like to get them properly formatted for release. If you have any you'd like to donate drop me a line using this contact form.

Split function in SCL

VBA has a function called Split that is handy for parsing strings to an array based on some delimiter.  All you have to do is dim out an array variable and say:
ArrayVar = Split( "the,split,string" , "," ) and your ArrayVar will contain three strings, "the" , "split", and "string".
There is no in-built function in SCL to do this, so I have put together something that should work in a similar fashion.  I have named the function the same, though the difference here is because SCL's arrays are fixed in size, where with VBA you can dynamically size arrays.
To get around this, you must first declare a String array in the Var section of your calling procedure (or globally), and pass that into the Split function as the last argument.  You must also provide the upper bound of the array to the function.  This upper bound is not the highest index of the array (which is one minus the size of the array), but just the size of the array.  One easy way to use this information is to declare your arrays using Const's denoting the size of the array.  For example:
procedure some_proc()
Const
arr_size 10
Var
my_array : Array[ arr_size ] of String
........
So, in this case, if you wanted to split a string into my_array, you would do this:
num_elems = split( 'some delimited line' , ' ' , arr_size , my_array )
where num_elems is the number of chunks of text split into the array.  If there are more chunks than there are bins in your array, any trailing chunks will be ignored, and no error will be raised.
I have tested this routine a bit, so it should work fairly well, even with multi-character delimiters.  On a side note, you should put this routine into your always available routines in QUEST.

routine split( the_line : String ; the_delim : String ; max_elements : Integer ; Var the_array : Array[] of String ) : Integer
Var
write_idx , delim_idx : Integer
check_char : String
Begin
while( len( the_line ) > 0 ) do
if( write_idx < max_elements ) then
delim_idx = index( the_line , the_delim , 1 )
--write( 'delim_idx=' , delim_idx , cr )
if( delim_idx > 0 ) then
the_array[ write_idx ] = leftstr( the_line , delim_idx - 1 )
the_line = rightstr( the_line , len( the_line ) - delim_idx - len( the_delim ) + 1 )
else
-- no more delimeters left - write the last of the_line to the array
the_array[ write_idx ] = the_line
the_line
= '' -- empty the_line so we break from the while loop
endif
write_idx = write_idx + 1
else
the_line = '' -- empty the_line so we break from the while loop
endif
endwhile
return write_idx -- return number of pieces written to array
End

Tuesday, August 18, 2009

Evaluating string expressions in SCL

I have found a few cases where it would be better to allow a user to enter not just numbers, but some expression that can be evaluated to a number. An simple example of this is just evaluating calculations, like "10+2*(8/5)". Ordinarily, if someone entered this and you were expecting a number, you may have a real problem. But luckily QUEST provides a means for evaluating strings to Real values, or even to String values.

The method involves calling out to BCL to evaluate an SCL expression, returning either a String or Real value. The associated BCL commands are SCL_NUMERIC_EXPR and SCL_STRING. You just pass in the SCL expression and read the resulting BCL_MSG variable as the result.

Here are two SCL routines that make these BCL commands a bit easier to use:

BCL_VAR
bcl_msg : String
routine evaluate_scl_expr( the_string : String ) : Real
Var
bcl_err : Integer
Begin
bcl_err = bcl( "SCL_NUMERIC_EXPR('" + the_string + "')" )
return val( bcl_msg )
End

routine eval_scl_string( the_expr : String ) : String
Var
bcl_err : Integer
Begin
bcl_err = bcl( "SCL_STRING_EXPR('" + the_expr + "')" )
return bcl_msg
End

Tuesday, August 4, 2009

Quest_Geom

I've used the same solution Martin described, and clumsy is an understatement, but it has worked fairly well for me in the past.  From what I've seen of the format, it's similar to the wavefront obj format, if that helps...

 

Here's an overview of how a basic QUEST geometry file is laid out, just from my messing around with these files...I have no expertise in how the format works, this is all based on observation and guesses.

 

All these observations are based on a basic rectangular block I created in QUEST D5R18.

 

Basically the file starts with the number 12, probably a version number or something, then some vector transformation information, probably for locating and rotating the geometry, though I've never messed around with that.  After all this information there is an asterisk indicating the geometry section is beginning.  I've found that older geometry files don't have this asterisk to delineate between geometry sections.

 

For a basic rectangular box, there are 8 points, for each of the 8 corners on the box.

After the first asterisk, there is a number indicating the number of points in the geometry.  Points serve as vertices for lines.  After listing the number of vertices, you list out the xyz location of each vertex.

 

After the vertices you list out something, and I either never knew or forgot what it is, but the geometry I'm looking at now has a zero.

 

Next we indicate the number of and list out all the lines in the geometry.  So if we have a rectangular box we have a total of 12 lines, as you can see in the diagram below.

To describe a line, we have to provide the index of each of the two vertices in the line.  QUEST basically reads the initial list of points in as a zero-indexed array, which means the first entry is point number zero in the array.

In this case, line 1 is composed of points 1 and 2, so our first line is represented as "1 2"

Next we indicate the number of and list out all the polygon faces in the geometry.  So if we have a rectangular box we have a total of 6 faces, as you can see in the diagram below.

The definition of a polygon is simply a list of the line indices composing that face preceded by the number of lines composing the face.  The direction of the line makes a difference, here.  The lines of a polygon must all go in the same direction - either clockwise around the face or counter-clockwise.  So, if a line is defined backwards from the direction of the other lines in the polygon, you must place a minus sign before that line number, and QUEST will interpret that and represent the polygon properly.

The last section we'll discuss is text.  I'm not entirely sure where the text section goes, but with the simple polygon block example, it seems we can just put the text section after the polygon face section.

To define the text, you must first provide the number of text objects to place on the geometry.  Next, start listing out the text values.  The first line of a text definition is the text string value.  The next four lines after that represent the location/rotation transform for the text placement.  To be honest, I'm not really sure how to interpret this.  At some point I'll try to post some routines I built for reading and writing these rotation matrices, but in the mean time, to work with text, there is an easier way.  Basically, just build your basic geometry, and manually place the text, save that out, and copy the transform matrix and use that.

So what we end up with with all this information should be a simple plain text file that we can feed into QUEST to build some simple rectangular box.  Here it is:

 

I hope this has helped give some basic idea of how to build simple geometry files without using QUEST.  Leave a comment if you have any questions about QUEST geometry that you'd like to discuss.


Thursday, July 23, 2009

eVSM to QUEST demo video

Here's a youtube video showing the eVSM to QUEST tool I've been developing at the Connecticut Center for Advanced Technology (CCAT).
If you're a QUEST user and have the eVSM software, and would like to try this functionality out, visit http://modsim.ccat.us/eVSM_Utilities and let us know you're interested. I'll be in contact when the time comes.

Friday, July 10, 2009

IIE Annual Conference 2009 - Applied Solutions presentation

Here are the slides I presented this year at the IIE Annual Conference - Applied Solutions session. It shows some of the work I've been doing with bridging the eVSM Value Stream Mapping software with QUEST.

More to come later...


Wednesday, July 1, 2009

eVSM to QUEST Interface

eVSM to QUEST Interface

 

Overview

The CCAT eVSM Utilities tool with the DELMIA QUEST (http://3ds.com) option allows you to export Value Stream Map information to the DELMIA QUEST (QUeuing Event Simulation Tool).

 

CCAT has developed a set of Simulation Control Logic (SCL) macros in QUEST for importing plain text files in tab-delimited format, and transforming that data into QUEST modeling constructs.

 

The CCAT eVSM Utilities makes it very easy to generate the plain text files and automates the subsequent import within QUEST.

Terminology

  • QUEST - QUeuing Event Simulation Tool.  This is Discrete Event Simulation Software provided by Dassault Systemes, and is used for the modeling and simulation of stochastic manufacturing systems
  • eVSM - electronic Value Stream Mapping software (http://evsm.com).
  • NVU - Name-Value-Unit.  An NVU is a construct within eVSM for holding process data.  The Name portion describes the name of the data, the Value is simply the data value, and Unit is the name of the unit the Value is measured in

Format Overview

The overall format for importing to QUEST is as such:

  • Two rows of column header information
  • One row delineating units in columns
  • Process information in rows

 

Each row of simulation data represents a process in QUEST.  Each column holds different data about the process.

 

There are three pieces of required data for each process in eVSM:

  • Cycle Time - The processing time of the process
  • Workstation - The name of the machine resource where the process is completed.  This may also be a comma-delimited list of machines, each machine being capable of running the same process
  • Product - The name of the product part class of the process

Configuration Files

The eVSM->QUEST SCL macros were built to be flexible enough to import VSM data from eVSM using whatever standardized NVU names you want.  An eVSM->QUEST configuration file has a .esm file extension, and is simply a list of setting names and values, similar to a QUEST .inc file.

 

The CCAT eVSM Utilities come with a pre-configured configuration file called QUEST_DIRECT.esm, and is stored in the DATA folder of the QUEST library you install the macros to.  This file is used as the default configuration file for exporting directly from eVSM.

 

Aside from mapping NVU names to data fields the SCL builder macros need, there are also several high-level settings that can be used to control how the SCL builder macros run.  The following are descriptions of several of these settings:

  • CELL_DELIM - The delimiting character between columns in the input text file.  By default this is tab, but a comma can be used (and can be generated by MS Excel)
  • COMMENT_LINE - This character tells the SCL builder macros that a line is not to be read as a process.  The default value is the # character
  • Geo - These settings allow you to specify the default geometry for buffers and machines
  • Workcell spacing options - These settings allow you to specify the spacing from a machine where the input and output buffers are to be placed.  You may also offset the location of the machine labor point in the y-axis using the LABOR_Y_SPACING setting
  • Logic - These settings allow you to specify the logics to assign to various elements by default (in-buffer routing logic, process logic, etc...).  The SCL file in which the logics are located must be compiled before the build.  This can be done by listing your logic files in your configuration file with the variable name as SCL_FILE#, where # is a number between 1 and 5, and where no two listed logic files share the same number.  These files will be compiled before the SCL builder macros execute

Data Overview

As previously mentioned, there are three required pieces of information you must provide for a process in eVSM.  In reality, there are six pieces of data the SCL builder macros need in order to build a model; however, the following three pieces of data are automatically populated for you within eVSM :

  • Operation- The name of the process
  • Tag - A unique identifier for the process (no other process can have the same tag, but may have the same name)
  • Next Op - The Tag of the process downstream of the current process.  This is used for identifying process sequences and routings

In addition to this required information, there is a set of optional NVUs you may provide to the SCL builder macros:

  • Changeover Time - The time required to change a machine from one process to another - modeled as a setup process in QUEST, between each process on the machine
  • Load Time - The time required to load a part onto a machine before the cycle process begins.  This is modeled as a setup process and is executed when the same process is performed twice in a row.  This means the load time must be included in changeover time, as the first load time is not run
  • Operator - The name of the laborer class to be required on a process.  If a process has a changeover and/or load time assigned to it, the operator is assigned to the load/changeover process(es).  Otherwise the operator is assigned to the cycle process
  • Operators - Depending on the assign_all_operators_to_process setting in the configuration file used.  If the setting is 0, then the operators value represents the number of laborers in the labor class.  If the setting is 1, then that number of operators is assigned to the process the laborer is assigned to
  • Num Elements - The number of elements in the machine class
  • MTTR/MTBF/Uptime - This is machine reliability data commonly used in simulation. Two of the three listed NVUs must be present to properly assign QUEST failures to the machines.  If Uptime is provided, the NVU not present (MTTR or MTBF) is calculated based on the Uptime value and whatever value is present
  • Scrap - The rejection rate of the process.  The rejection rate tells QUEST what percentage of parts to scrap, which means the parts are destroyed, and not repaired.  The SCL builder macros do not currently support part repair
  • GeometryThe display geometry file name for the listed Workstation.  This will override the configuration file level setting
  • TimeToNext - The time for the part to get to its next operation.  The time delay is executed on the output buffer of the machine which the process is run on, and is not resource-constrained.  The part enters the buffer, and after the TimeToNext is elapsed it moves on to the next process.  This may be used for modeling outside operations which are not being modeled.  This value may be provided as an SCL expression (meaning you may use build-in distribution functions)
  • Shifts - The number of shifts the listed Workstation of the process works.

Units

The builder assumes that the data file coming in was created using eVSM.  As of eVSM version 4, units are placed in the third row, and so the builder assumes row 3 of the data file it reads contains the units for all the NVU's.  The builder will look to these units to determine the units for: cycle time, setup time, load time, IAT, time to next operation, and Mean Time To Repair (MTTR).  If no units are found for a specific variable, default values are assumed (minutes for cycle, setup, and load times; hours for IAT & MTTR)

Location/Layout Information

The SCL builder macros need to know where to put the machines listed in the Workstation NVU of a process.  This can be provided multiple ways:

  • Shape of the Value Stream Map
  • Locations from an eVSM Spaghetti Chart
  • Manual location

When building from eVSM, you only have a choice between laying out machines in the shape of the VSM, or getting locations from an eVSM Spaghetti Chart. 

 

If you choose the shape of the VSM, the SCL builder macros will place a machine in the location of the first process that uses that machine in its Workstation NVU.  To locate based off an eVSM Spaghetti Chart, you must provide the name of the page the Spaghetti Chart is on, and the column from Excel corresponding to the place names set in the eVSM Spaghetti Chart.  For example, if you name all your Spaghetti Places based on the values from your Workstation NVUs, you would tell the builder interface in eVSM to use the Workstation column for locating machines.

CCAT eVSM Utilities interface to SCL Builder Macros

Layout Options

Select a radio button to tell the builder how to locate machines in QUEST.  Using "No layout" or "Layout file" will locate machines in the shape of the VSM.  Using "Layout from page" requires that you specify the name of the eVSM Spaghetti Chart page, and the name of the column in the eVSM Excel Calculator spreadsheet which matches up processes with the location names in your eVSM Spaghetti Chart

 

esm File

You may specify an esm configuration file, if you have a customized file you need to use for the export.  If left blank, the default QUEST_DIRECT.esm is used.

 

Pre- and Post-Build BCL Commands

You may specify a list of BCL commands to be executed before and after the SCL builder macros are executed.  You may wish to compile logic files before the build, or to run different SCL macros after the builder execution.

 

These commands should be stored in the eVSM Excel Calculator spreadsheet.  The worksheet the BCL commands are stored in should be named the same as the page your VSM is in, with "_BCL" appended on the end of the name.  The easiest way to set this up is to click the "Import BCL from Excel" button, and the worksheet will be created, if necessary.

 

You must specify which columns contain which commands, where Pre-build BCL commands must have " *--PRE_BUILD_BCL*" in the first row cell of the pre-build commands column.  " *--POST_BUILD_BCL*" must be in the first row cell of the post-build commands column.

 

Build Process

Once you have your settings complete in the CCAT eVSM Utilities, click the "Build" button.  This will execute the eVSM Calculator, after which the CCAT eVSM Utilities will export the machine locations to Excel, as well as the Next Op data for each process (based on the connections between processes in your VSM).  Finally, the CCAT eVSM Utilities will export your Excel data to a tab-delimited text file, and start QUEST and tell it to execute a BCL script generated with your pre- and post-build BCL commands, as well as commands to run the SCL builder macros.

 

The SCL builder macros begin by reading the spreadsheet data to an internal string array, and identifying all the column names, as well as reading units for different NVUs.  Next, the builder copies data it has mapped to specific fields to a different string array, and begins the build process.

 

The build process begins with building part classes based on the Product column, then it builds a process for each row in the data array.  Next, the process products and requirements are set based on the links between processes and their products.  For instance, if two processes both have a process with tag "B10", the builder will interpret the process as an assembly process, and will require one of each of the Product part classes from the feeding processes.  Then, based on the Product of the process, the process will be set to either pack one of the requirements into the other, or it will create a new part (if the product is not one of the requirements).

 

Next, shifts are added to machines based on the Shift data, if provided.  If a machine is listed as having more than three shifts, then each shift is set as a time of 24 hours divided by the highest shift number found.  If the maximum shift number is 3 or less, then each shift is set as 8 hours.  Later on in the build, these shifts will be assigned to your machines.  If the shift times need to be changed this can easily be done either manually or through BCL.  The hardest part is done for you, which is creating and assigning the shifts to machines.

 

Next, workstations are created.  A workstation is just a machine, an input buffer, and an output buffer.  An input buffer receives and holds work until its machine can take it.  An output buffer holds finished work until it can be moved to the next workstation. 

 

As noted above, if TimeToNext is provided for a process, the workstation's output buffer will hold the part for whatever that TimeToNext value is.

 

Each workstation gets added to its own group so you may easily move a workstation around within a model.

 

Next, the builder will build connections between workstations.  This is done based on the requirements of the processes on different machines.

 

Next, laborers are assigned to processes.  If a process has loading or changeover associated with it then the laborer is assigned only to the loading/changeover process(es).  Otherwise the laborer is assigned to the cycle process.

 

Next, loading and changeover machines are assigned to machines.  After that, the builder assigns a process sequence to each part class, based on the chain of requirements and products of all the processes previously built.  One noted problem with this is that the BCL statement to build a process sequence must contain the names of all the processes in the sequence, rather than simply adding one process to the sequence.  This means that you may run against a string size limit when building a model with many long-named processes.  To fix this, modify the BIG_STRING_SIZE constant in the build_from_vsm_generic.scl file to something larger.

 

The builder then assigns action logics, if applicable, as listed in the configuration file.  Next comes machine failures, which are created and assigned if a process lists a machine's reliability as detailed above.

 

Lastly, the builder goes back to the original input array and finds any data columns that were not mapped to known constructs, and creates user attributes on the processes holding that data.  This means you can very easily pass data between eVSM and QUEST without customizing the build_from_vsm_generic.scl file.

 

The builder then cleans up any build errors and data arrays, and saves the model to QUESTLib\Last_Build_Model.mdl.  Also, the builder creates a BCL script of all BCL commands that get executed throughout the build process, and a log file is kept at QUESTlib\Output\writer_log.txt.


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.