Sunday, May 1, 2011

A more modern example of Intercom

In the last post, I introduced Intercom with an old example I wrote using VB6.  This post will be a much more modern example using C#.

I’ve also included a “Round Trip” example where the command is initiated via CC3.  By adding this small macro, you now have a command that draws a diamond on the screen.

Code Snippet
  1. MACRO DIAMOND
  2. GP TEMP ^DCenter:
  3. SENDM 2 TEMP
  4. ENDM

What this does is as the user to get a point (GP) and then send it via Intercom to the c# application.

Once it gets to the c# code, it takes the string from CC3 (all data sent from CC3 via Intercom is in strings), splits it on the comma and converts the two substrings into doubles.  Then it creates a command string and passes it back to CC3.

Code Snippet
  1. var strNumbers = System.Text.Encoding.ASCII.GetString(bytMsg).Split(',');
  2. var x = double.Parse(strNumbers[0]);
  3. var y = double.Parse(strNumbers[1]);
  4.  
  5. var strDiamond = "LINE \n" +
  6.                     (x - 100) + "," + y + " \n" +
  7.                     x + "," + (y - 100) + " \n" +
  8.                     (x + 100) + "," + y + " \n" +
  9.                     x + "," + (y + 100) + " \n" +
  10.                     (x - 100) + "," + y + " \n\n" +
  11.                     "ENDM";
  12.  
  13. icSendMsg(20, strDiamond);

Here is a link to the C# portion

Monday, April 11, 2011

An introduction to Intercom

Intercom was originally written in C code for inclusion in C programs. For C programmers this was quite acceptable. Unfortunately the vast majority of non-professional programmers do not work in C, so something needed to be done.

I've taken the original C code and placed it into a plain C DLL. Most higher level languages like C#, VB.Net, VB6 and Delphi can utilize the subroutines in a C DLL. When I originally wrote about Intercom (way back in 1999 – You can tell, because the title of the example application reads: “Intercom with CC2), I was a professional Visual Basic programmer I used VB6 as my language of choice in the following example.

First, what is intercom? Intercom is code that creates a memory-mapped file that both CC3 and your program can use. What this means is that in memory a chuck of space is set aside, half for you and half for CC3. You can only read your half and you can only write to CC3's half. CC3 on the other hand can do just the opposite.

Whenever a new message is written, a 'New mail flag' is raised. CC3 looks for a raised flag automatically. Your program needs to check periodically.

Second, what is it that is sent back and forth between CC3 and a program? Intercom sends an integer and a string back and forth. To CC3 these have very strict meanings. The integer is part of the name of the macro it will now run and the string is used as input data.

If you sent CC3 via intercom the number 123 then the macro CC3 will attempt to run in named RCV123 and the string will now reside inside the variable RCVDATA.

CC3 ships with an important macro: RCV20.  If you run the exe in the VBIntercom zip file (Intercom.exe) and enter 20 and LINE 0,0 1000,1000 you will see.

image

See? The macro RCV20 contains nothing but RCVDATA in it. This macro will run whatever text is sent to it, so this means that the calling program can dynamically build and run scripts/control CC3.

To connect to the DDL, you need to use Declare Statements. See the code in the VB6 form for the exact syntax, later l will post C# examples.

Here is a link to the original C code for Intercom.

Here is a link to the Intercom code wrapped into a plain C DLL plus the old VB6 example.

Thursday, December 9, 2010

Creating a Path element without adding it to the Drawing List

Wow, its been a while since I’ve written a post here.  The holidays are always a busy family blur.  Well, here is the latest installment …

The path/poly element is the most used element in CC3.  You really cannot get much done with it.  Try building anything in CC3 without it (Sure you can just use more primitive elements and use multipoly & group for joining and filling, but really you are just re-creating the path/poly element).  So if you want to get something done in an XP, odds are you are going to be working with paths/polys.

If you have been following this blog or the cc2-dev-list on groups.yahoo.com, you have seen code that creates path.  The code always creates a empty drawing list element then works on this record entity to “change” it into a path/poly.  I’ve always had a bit of a bad feeling about that.  It seems that it would be more “correct” to only add completed elements to the drawing list.  So, after quite a bit of frustration, I’ve successfully  worked out the code to create a path/poly element independent of the drawing list.

The code below creates a closed path (poly) with with four points:

Code Snippet – Build a PATH2 Element
  1. PATH2 *pathPtr = (PATH2*)malloc(sizeof(PATH2));
  2.  
  3. pathPtr->CStuff.ERLen = sizeof(PATH2);    // entity record length
  4. pathPtr->CStuff.EType = ET_PATH2;         // entity type code
  5. pathPtr->CStuff.EFlags = 0;               // erase/select bits
  6. pathPtr->CStuff.EFlags2 = 0;              // extra flags
  7. pathPtr->CStuff.EColor = 1;               // entity color
  8. pathPtr->CStuff.EColor2 = 1;              // fill (2nd) color
  9. pathPtr->CStuff.EThick = 0;               // pen thickness 0..25.4 mm
  10. pathPtr->CStuff.WPlane = 0;               // workplane (0 = XY plane)
  11. pathPtr->CStuff.ELayer = 1;               // layer
  12. pathPtr->CStuff.ELStyle = 0;              // line style (0=solid)
  13. pathPtr->CStuff.GroupID = 0;              // group id (0 = not grouped)
  14. pathPtr->CStuff.EFStyle = 0;              // fill style (0=hollow)
  15. pathPtr->CStuff.LWidth = 0.0;             // line width
  16. pathPtr->CStuff.Tag = 0;                  // entity tag id
  17. pathPtr->Path.SmType = 0;                 //SmType
  18. pathPtr->Path.SRes = 8;                   //SRes
  19. pathPtr->Path.SParm = 0.0;                //SParm
  20. pathPtr->Path.EParm = 0.0;                //EParm
  21. pathPtr->Path.Count = 0;                  //Count
  22. pathPtr->Path.Flags = 0;                  //Flags
  23. pathPtr->Path.unused = 0;                 //unused
  24.  
  25. GetCStuff((pENTREC)pathPtr);
  26.  
  27. pathPtr = (PATH2*)realloc(pathPtr, sizeof(PATH2) + sizeof(GPOINT2) * 4);
  28.  
  29. pathPtr->CStuff.ERLen = sizeof(PATH2) + sizeof(GPOINT2) * 4;
  30.  
  31. pathPtr->Path.Nodes[0].x = 0.0;            //Nodes[0]
  32. pathPtr->Path.Nodes[0].y = 0.0;            //Nodes[0]
  33. pathPtr->Path.Nodes[1].x = 0.0;            //Nodes[1]
  34. pathPtr->Path.Nodes[1].y = 1000.0;         //Nodes[1]
  35. pathPtr->Path.Nodes[2].x = 1000.0;         //Nodes[2]
  36. pathPtr->Path.Nodes[2].y = 1000.0;         //Nodes[2]
  37. pathPtr->Path.Nodes[3].x = 1000.0;         //Nodes[3]
  38. pathPtr->Path.Nodes[3].y = 0.0;            //Nodes[3]
  39.  
  40. pathPtr->Path.Count = 4;
  41. pathPtr->Path.EParm = 4.0;
  42. pathPtr->Path.Flags = NL_CLS;
  43.  
  44. pENTREC pEntRec = DLApnd(NULL, (pENTREC)pathPtr);
  45.  
  46. EDraw(pEntRec);
  47.  
  48. free(pathPtr);

Thursday, November 4, 2010

Quick development tip

For those of us that are not super c/c++ gurus, like me, debugging an XP add-on can be quite challenging. Over the years I've tried numerous different ways to see into the inner workings of the code.
  • I first tried placing lots of InfoBox() calls to report on the code state.  This worked to a point, but when I needed to see what way going on in a dynamic cursor, or anywhere that the user needed to interact with CC3, the InfoBox() tended to interfere.  Plus building a string, that usually included numbers, just to output it to a message box seemed like a lot of work.
  • Then I added a bunch of code to write to a text file.  This worked great but is was quite a bit of code and if I forgot to remove it, the end users got debug files that the shouldn't have.  Plus you still needed to do all that code for creating your output string.  And, if you wanted the file readable, you needed to include newlines, tabs etc.
Well, when we are first all taught c/c++ it is via the command line.  The command printf/cout is king.  We learn to output data to the console window in very simple ways.  As we start to develop more and more complex applications, we could, for development purposes, interject tons of these little commands to see exactly what was happening.

Unfortunately, with the development of windowing software we no longer have the console window.  Or do we?

If you are developing an XP add-on, (or any windows application for that matter) adding a console window is quite easy.  Simply slip into your code two commands: AllocConsole & FreeConsole.

Simply change your code to match the code bellow and start some searious debugging!

Code Snippet - Display Console Window
  1. BOOL WINAPI DllMain (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
  2. {
  3.     switch (dwReason)
  4.     {
  5.         case DLL_PROCESS_ATTACH:
  6.         {
  7.             AllocConsole();
  8.             MyXP.ModHdl=hDLL;
  9.             XPRegCmd(&MyXP);
  10.             break;
  11.         }
  12.         case DLL_PROCESS_DETACH:
  13.         {
  14.             FreeConsole();
  15.             XPUnregCmd(&MyXP);
  16.             break;
  17.         }
  18.     }
  19.  
  20.     return TRUE;

Monday, October 25, 2010

Code name: Map Invoker

As Simon mentioned in previous post here, I am working on an application, which I am developing under the code name "Map Invoker".  It will generate random towns, from small little hamlets to large walled cities.  I've been concentrating on the algorithm side and am truely quite a ways towards a "feature complete" logic set.

So Simon was correct in stating that "He’s gone beyond proof of concept".  Soon I will be turning from generation to the interface so that all the parameters that I've included in the algorithms are exposed to the user so that the user can create an incredable range of different towns.

Once the paramter inputs are down and tested, then we will be looking into adding all the "garnish" that makes programs like this come alive.  So if you have any ideas, practical or "pie in the sky", I'd love to here them.  Either post your idea or email me at: SAUNDERL (at) HOTMAIL (dot) COM.

Tuesday, September 28, 2010

The Spiral Command - In Three Parts (Part #1)

The Spiral Command - In Three Parts (Part #1)
In this post and the next two posts, I will be building a spiral command.  In this post we will be dealing with the algorithm.  When we are done with this post, we will have a command that draws a spiral that would have 20 loops if it started in the middle, but we also told the algorithm to not draw the innermost 10 loops.  We have also hard currently coded the command to draw the spiral with a distance of 10 between each loop.

I will be using the name MYSPIRALM for this temporary command.  When finished, this will be the command for the macro version.

Note:  There is a naming convention that states if you have both a dialog driven command and a comand that is all command line for macros, the macro version ends with an M.

 Hardcoded values:
  • Distance Between Loops: 10
  • Number of Inner Loops Not Drawn: 10
  • Total Number of Loops: 20
  • Clockwise/Counter Clockwise: Clockwise
  • Growth Between Loops: None
Our next post will implement RecData's to get the values that we currently are hardcoded.  Then, we will finish up with a dialog driven command.

Friday, August 20, 2010MyLine Command (A Complete Example)
We have finally reached the point where we can produce an entire example. In my previous post I explained how, conceptually, this command will work. Now, here is the actual code for the command along with a lot of comments.

I have also copied and commented out the definitions for the relevant elements that we are going to create.

I hope that you fire up Visual Studio and try building this code; if you are impatient though and want to just run it, here

Code Snippet - MySpiralM Command
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. //    File Name: MySpiral.cpp
  4. //    Written by: L. Lee Saunders
  5. //    (C)2010 Beer & Pretzel Games
  6. //    All rights reserved
  7. //
  8. ////////////////////////////////////////////////////////////////////////////
  9.  
  10. #include <windows.h>
  11. #include <math.h>
  12.  
  13. extern "C"
  14. {
  15.     #include <xp.h>
  16.     #include <Extend/Mysvc.h>
  17. }
  18.  
  19. extern XP MyXP;
  20.  
  21. ////////////////////////////////////////////////////////////////////////////
  22.   
  23. void XPCALL DrawSpiral (int Result,int Result2,int Result3);
  24. GPOINT2 CenterPoint;
  25.  
  26. FORMST(lpszCenterPoint,"Center Point:\0")
  27.   
  28. RDATA PCenterReq =
  29.    { sizeof(RDATA), RD_2DC, NULL, RDF_C, (DWORD*)&CenterPoint,
  30.   (DWORD*)&lpszCenterPoint, RDC_XH, DrawSpiral, NULL, NULL, 0, NULL, 0};
  31. ////////////////////////////////////////////////////////////////////////////
  32.   
  33.   
  34. ////////////////////////////////////////////////////////////////////////////
  35. void XPCALL MySpiralM (void)
  36. {
  37.     ReqData(&PCenterReq);  //get center point
  38. }
  39. ////////////////////////////////////////////////////////////////////////////
  40.  
  41.  
  42. ////////////////////////////////////////////////////////////////////////////
  43. void XPCALL DrawSpiral (int Result,int Result2,int Result3)
  44. {
  45.     if (Result != X_OK) { CmdEnd(); return; }
  46.  
  47.     float Growth = 1;
  48.     float LoopDistance = 10;
  49.  
  50.     int Rotations = 20;
  51.     int InnerLoopsToSkip = 10;
  52.     const char * Direction = "0\0";
  53.  
  54.     pENTREC pEntRec;  //Create a new drawing database entity
  55.     
  56.     float CenterX = CenterPoint.x;
  57.     float CenterY = CenterPoint.y;
  58.  
  59.     int iDirection;
  60.  
  61.     MarkUndo();
  62.  
  63.     // Insert an empty path into the drawing list
  64.     pEntRec=DLApndE(NULL, ET_PATH2, sizeof(PATH2)+sizeof(GPOINT2));
  65.  
  66.     // Get the common stuff
  67.     GetCStuff(pEntRec);
  68.  
  69.     // Set the smoothing to Parabolic Blend (through-point)
  70.     pEntRec->Path.Path.SmType = SM_PB;
  71.  
  72.     // Set the centerpoint of the spiral
  73.     pEntRec->Path.Path.Nodes[0].x = CenterPoint.x;    
  74.     pEntRec->Path.Path.Nodes[0].y = CenterPoint.y;
  75.  
  76.     // How many points to draw per circle
  77.     const double STEPS_PER_ROTATION = 50;
  78.     
  79.     // Amount to add to angle at each step
  80.     double increment = M_PI/STEPS_PER_ROTATION;  // Splitting up
  81.  
  82.     double theta = 2*M_PI*InnerLoopsToSkip;  // This allows for an open area
  83.     // inside the spiral
  84.  
  85.     // Determines if the loops rotates clockwise or counter clockwise
  86.     iDirection = strcmp(Direction, "0") == 0 ? 1 : -1;
  87.  
  88.     while(theta < (Rotations * 2) * M_PI)
  89.     {
  90.         pEntRec=DLResize(pEntRec, pEntRec->Path.CStuff.ERLen +
  91.           sizeof(GPOINT2));
  92.  
  93.         pEntRec->Path.Path.Nodes[pEntRec->Path.Path.Count].x=(float)
  94.           (CenterX + theta * cos(theta) * (1/(M_PI * 2)) * LoopDistance *
  95.           iDirection);
  96.         pEntRec->Path.Path.Nodes[pEntRec->Path.Path.Count].y=(float)
  97.           (CenterY + theta * sin(theta) * (1/(M_PI * 2)) * LoopDistance);
  98.         
  99.         pEntRec->Path.Path.EParm=(float)pEntRec->Path.Path.Count;
  100.         pEntRec->Path.Path.Count++;
  101.         
  102.         LoopDistance = LoopDistance * Growth;
  103.         theta = theta + increment; //If theta grows faster then increment,
  104.         //it will have wider spaces between each loop    
  105.     }
  106.                 
  107.     EDraw(pEntRec);  //Draw our line
  108.     ShowChanges();   //Needed for CC3 to "Show Changes" to the DB
  109.  
  110.     CmdEnd();
  111.     return;
  112. }
  113. ////////////////////////////////////////////////////////////////////////////

Thursday, September 2, 2010

How to walk the "Drawing List"

In trying to decide what element of CC3 XP programming I was going to blog about next, I started to re-read all of the old posts on the cc2-dev-l email list @ groups.yahoo.com. 

Wow, I had forgotten how much valuable information was out there. If you have the time (And it will take a bit of time) just start at the beginning and read through the threads.  I promise that you will learn something – I did!

I found a request by Linda Kekumu.  It seemed that she had a large number of maps that had lots of Notes attached to them and they were wrong.  She needed a quick way to clear out all the notes on a map, instead of manually removing them one at a time.  Peter responded with this dash of code.

The reason I selected it for this post is that it is probably the smallest piece of code that displays how to use DLScan.  Now DLScan does exactly as the name says, it scans the “Drawing List” (i.e. the drawing database) and calls a function for every “identified” element.  I say “identified” because there are times when you only want to look at a subset of the entire Drawing List.

In this new XP command, we scan the Drawing List with the DLS_Std flag, telling DLScan that we want to scan the Drawing List for “all standard all non-erased elements”.

Then in our called function (ClearNotesScan) we check the EType of each identified  element (pEntRec) .  If the EType equals ET_Note, we know it is a Note and we call DLErase to remove the element from the Drawing List.

So, with this, you can now scan through a map and look at every identified element!

Code Snippet – MyClearNotes Command
  1. /////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Program: CC3DeveloperBlog.dll
  4. // File Name: MyClearNotes.cpp
  5. // Written by Peter Olsson (Copied here by L. Lee Saunders)
  6. // (C)2010 Beer &amp; Pretzel Games
  7. // All rights reserved
  8. //
  9. /////////////////////////////////////////////////////////////////////////////

  10. #include <windows.h>
  11. #include <math.h>

  12. extern"C"
  13. {
  14.     #include <xp.h>
  15.     #include <math.h>
  16. }
  17. extern XP MyXP;
  18. /////////////////////////////////////////////////////////////////////////////


  19. /////////////////////////////////////////////////////////////////////////////
  20. DWORD XPCALL ClearNotesScan(hDLIST hDList, pENTREC pEntRec, PARM p1, PARM p2)
  21. {
  22.     //If the Entity Record is of type "Note" then erase the Entity from DL
  23.     if(pEntRec->CStuff.EType==ET_NOTE) { DLErase(pEntRec); }
  24.     return 0;
  25. }
  26. /////////////////////////////////////////////////////////////////////////////


  27. /////////////////////////////////////////////////////////////////////////////
  28. void XPCALL MyClearNotes(void)
  29. {
  30.     //Description copied from FCW32.TXT
  31.     //---------------------------------------------------------------
  32.     // ClearSel - Clear All Selection bits
  33.     //---------------------------------------------------------------
  34.     ClearSel();

  35.     //This sets an undo point so that every action after this to the end of
  36.     //the command will be reversed if the user selects UNDO.
  37.     MarkUndo();

  38.     //Drawing list scan. We are interested in only the first three parameters
  39.     // Param #1: Drawing list - If null, it is the main list
  40.     // Param #2: Our Callback function for every identified element in the DL
  41.     // Param #3: DSFlags. How we identify what DL elements we want
  42.     DLScan(NULL, ClearNotesScan, DLS_Std, 0, 0);

  43.     //End the command
  44.     CmdEnd();
  45. }
  46. /////////////////////////////////////////////////////////////////////////////