Showing posts with label CC3. Show all posts
Showing posts with label CC3. Show all posts

Saturday, February 4, 2012

Creating C# objects mapped to CC3 objects

When Simon and I discussed how to fully integrate the city creator, code named “Map Invoker”, the idea of it working like a Wizard surfaced. 

With a wizard like interface, each layer of objects (Waterways, Roads, Walls, etc.) would each be generated by a different page on the wizard.  A “generate” button would draw the objects directly in CC3.  If the results were not what the user wanted, the user could press the “generate” button again and the old objects would be removed and the new ones drawn.  Once the user liked the output, the “next” button would take the user to the next wizard page.

Now this sounded like a great design to me.  But …. I had some technical issues to overcome and some design work to make life a little easier.

The main technical issue was that the built-in dialog system was not made to ever run custom code and call XP functions while the dialog was still visible.  This was solved by using C# to write the Dialog code and communicating with CC3 via a simple callback class and a delegate.  I will go into this solution in depth in a later post.  Right now I want to go over the design work I did to make life easier passing data back and forth between C++ & C#.

Here is the diagram of my classes thus far.  They are simple value classes, with no behavior.  In a previous experiment, I created a REAL object (data & behavior) but because of the fact that a real class crosses native and managed code, it worked great in a all C++/cli environment, but I could not use it in C#.  So I decided to split data and behavior into two separate dlls.  The cc3objects.dll, a C# set of value classes and cc3actions.dll, the C++/cli dll that implements the behavior.

diagram

Now, what this does is let me create an entire object, or set of objects that can be directly converted to native CC3 objects.  So, I actually build my objects in C# then pass them to my C++/cli XP dll.  Plus, once I get done with this project everyone will be able to use these dlls.

Thursday, October 6, 2011

Looking under the hood of the .FCW file format

First a warning – The .FCW file format is BINARY!  If you do not feel comfortable playing with bits and bytes, this may not be for you.  But if you do enjoy this type of challenge, the .FCW file format is one of the best ways to output to CC3.
Imagine you have a random maze generator and you want to output it to CC3.  You could export a script.  It would redraw your maze one line at a time.  
But there are also some problems with scripts:
  • You have to run them.  This may sound obvious but consider that you have to either know the text command to open and run a script file, or you need to know where in the menu system it is.
  • Scripts are slow.  CC3 will have to take your script and run it line-by-line.  Its as if you had set down and typed in the commands directly into CC3.
  • Scripts are not exactly fragile, but they are not very robust either.  And, if your script fails, your users are the ones that are going to get frustrated.
Where as if you exported a .FCW file, simply the action of opening the file is all that is needed. So … if you are still with me, here we go!
One last twist – the .FCW file could be compressed.
The .FCW file format is made up of many different “Blocks” of data.  The first 4 bytes of each block (except for the first block) contains the number of bytes that the following block contains.  It starts with the FileID block.  The FileID block is the only block that is guaranteed to be uncompressed.  This 128 byte block contains quite a bit of general info on the file.  It identifies what type of file it is to other programs, and the version and sub-version number of the file format is was built with.  Last, but not least, it informs the reader that bytes after byte 128 are compressed or not.  (To save an uncompressed file, after you have clicked "Save As ...", you will be presented with the save file dialog.  If you click on the options button, you will be presented with a small dialog box.  Uncheck the "File Compression" option)
For this first blog post on the .FCW file format, I will show you how to read a binary file into a byte array and how to display it, byte-by-byte in a textbox similar to all the binary editors display it.  Being able to look inside a binary file will come in very handy in the future.  Last but not least I will show you FILEID object.
Code Snippet – How to read an entire file into a byte array
  1. public byte[] ConvertFileToByteArray(string fileName)
  2. {
  3.     byte[] buffer;

  4.     using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
  5.     {
  6.         using (var binaryReader = new BinaryReader(fileStream))
  7.         {
  8.             buffer = binaryReader.ReadBytes((int)new FileInfo(fileName).Length);

  9.             binaryReader.Close();
  10.         }

  11.         fileStream.Close();
  12.     }

  13.     return buffer;
  14. }
Once the file is read into a byte array, I pull the first 128 bytes out of the file byte array, using an extension method to Arrays to create sub-arrays.
Code Snippet – SubArray extension method
  1. public static class Extensions
  2. {
  3.     public static T[] SubArray<T>(this T[] data, int index, int length)
  4.     {
  5.         var result = new T[length];

  6.         Array.Copy(data, index, result, 0, length);

  7.         return result;
  8.     }
  9. }
Then I feed the sub-array to my FILEID object.  This object converts the bytes into the fields of the FILEID object.  We can then check the Compressed property.  If the file is compressed, the rest of the data is meaningless, so if the file is compressed, I set the background color of the textbox to a Rose color.
Code Snippet
  1. using System;
  2. using System.Text;

  3. namespace DisplayBinaryFile
  4. {
  5.     public class FILEID
  6.     {
  7.         #region Fields
  8.         public char[] ProgId = new char[26];
  9.         public char[] VerText = new char[4];
  10.         public char[] VerTextS = new char[14];
  11.         public byte[] DosChars = new byte[3];
  12.         public byte DBVer;
  13.         public bool Compressed;
  14.         public byte[] Filler = new byte[78];
  15.         public byte EndFileID;
  16.         #endregion

  17.         public int Length { get; set; }

  18.         public FILEID(byte[] buffer)
  19.         {
  20.             ProgId = Encoding.ASCII.GetChars(buffer.SubArray(0, 26));
  21.             VerText = Encoding.ASCII.GetChars(buffer.SubArray(26, 4));
  22.             VerTextS = Encoding.ASCII.GetChars(buffer.SubArray(30, 14));
  23.             DosChars = buffer.SubArray(44, 3);
  24.             DBVer = buffer.SubArray(47, 1)[0];
  25.             Compressed = BitConverter.ToBoolean(buffer.SubArray(48, 1), 0);
  26.             Filler = buffer.SubArray(49, 78);
  27.             EndFileID = buffer.SubArray(127, 1)[0];

  28.             Length = 128;
  29.         }

  30.         public byte[] GetBytes()
  31.         {
  32.             var returnValue = new byte[Length];

  33.             Array.Copy(Encoding.ASCII.GetBytes(ProgId), 0, returnValue, 0, 26);
  34.             Array.Copy(Encoding.ASCII.GetBytes(VerText), 0, returnValue, 26, 4);
  35.             Array.Copy(Encoding.ASCII.GetBytes(VerTextS), 0, returnValue, 30, 14);
  36.             Array.Copy(DosChars, 0, returnValue, 44, 3);
  37.             Array.Copy(BitConverter.GetBytes(DBVer), 0, returnValue, 47, 1);
  38.             Array.Copy(BitConverter.GetBytes(Compressed), 0, returnValue, 48, 1);
  39.             Array.Copy(Filler, 0, returnValue, 49, 78);
  40.             Array.Copy(BitConverter.GetBytes(EndFileID), 0, returnValue, 127, 1);

  41.             return returnValue;
  42.         }
  43.     }
  44. }
Here is a link to the entire Visual Studio 2010 project

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;

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. /////////////////////////////////////////////////////////////////////////////

Friday, August 20, 2010

MyLine 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 is the dll.

Code Snippet - MyLine Command (A Complete Example)
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. //    Written by: L. Lee Saunders
  4. //    Date: 8/15/2010
  5. //    (C)2010 BeerAndPretzelGames.com
  6. //    All rights reserved
  7. //
  8. ////////////////////////////////////////////////////////////////////////////
  9. #include <windows.h>
  10.  
  11. extern "C"
  12. {  
  13.     #include <XP.H>
  14.     #include <Extend/Mysvc.h>
  15. }
  16.  
  17. #define XPID 0xF000
  18.  
  19. void XPCALL About(void);
  20. void XPCALL MYLINE(void);
  21.  
  22. char CList[]="MYLINE\0";
  23. PCMDPROC PList[]={About,MYLINE};
  24.  
  25. XP MyXP =
  26.    { 0, CList, PList, 0, 0, 0, XPID, 0, 500, 0, 0, 500 };
  27. ////////////////////////////////////////////////////////////////////////////
  28.  
  29.  
  30. ////////////////////////////////////////////////////////////////////////////
  31. FORMST(MyAPkt,"BLOG DLL commands:\n\n"
  32.        "\tMYLINE - Duplicating the Line Command #s\n\0")
  33.  
  34. void XPCALL About (void)
  35. {
  36.     FormSt(&MyAPkt,RSC(FD_MsgBox));
  37. }
  38. ////////////////////////////////////////////////////////////////////////////
  39.  
  40.  
  41. ////////////////////////////////////////////////////////////////////////////
  42. void XPCALL MYLINE2 (int Result,int Result2,int Result3);
  43. void XPCALL MYLINE3 (int Result,int Result2,int Result3);
  44.  
  45. //This is the definition of LINE2
  46. //==========================================================================
  47. //typedef struct
  48. //{
  49. //    CSTUFF    CStuff;              // entity properties
  50. //    GLINE2    Line;                // line properties
  51. //} LINE2;
  52.  
  53. //This is the definition of GLINE2
  54. //==========================================================================
  55. //typedef struct
  56. //{
  57. //    GPOINT2 p1;                    // starting point
  58. //    GPOINT2 p2;                    // ending point
  59. //} GLINE2;
  60.  
  61. //This is the definition of GPOINT2
  62. //==========================================================================
  63. //typedef struct
  64. //{
  65. //    float x;
  66. //    float y;
  67. //} GPOINT2;
  68.  
  69. //This is the definition of CSTUFF
  70. //==========================================================================
  71. //typedef struct
  72. //{
  73. //    int    ERLen;                    // entity record length
  74. //    unsigned char    EType;          // entity type code
  75. //    char    EFlags;                  // erase/select bits
  76. //    char    EFlags2;                 // extra flags
  77. //    unsigned char    EColor;         // entity color
  78. //    unsigned char    EColor2;        // fill (2nd) color
  79. //    char    EThick;                  // pen thickness 0..25.4 mm
  80. //    short    WPlane;                 // workplane (0 = XY plane)
  81. //    short    ELayer;                 // layer
  82. //    short    ELStyle;                // line style (0=solid)
  83. //    short    GroupID;                // group id (0 = not grouped)
  84. //    short    EFStyle;                // fill style (0=hollow)
  85. //    float    LWidth;                 // line width
  86. //    int    Tag;                      // entity tag id
  87. //} CSTUFF;
  88.  
  89. //This is the working structure that we will use to create all the lines
  90. LINE2 BuildL = {
  91.     sizeof(LINE2),                // entity record length
  92.     ET_LINE2,                     // entity type code
  93.     0,                            // erase/select bits
  94.     0,                            // extra flags
  95.     1,                            // entity color
  96.     1,                            // fill (2nd) color
  97.     0,                            // pen thickness 0..25.4 mm
  98.     0,                            // workplane (0 = XY plane)
  99.     1,                            // layer
  100.     0,                            // line style (0=solid)
  101.     0,                            // group id (0 = not grouped)
  102.     0,                            // fill style (0=hollow)
  103.     0.0,                          // line width
  104.     0,                            // entity tag id
  105.     0.0,                          // starting point: x
  106.     0.0,                          // starting point: y
  107.     1.0,                          // ending point: x
  108.     1.0                           // ending point: y
  109. };
  110.  
  111. FORMST(lpszFirstPoint,"1st point:\0")
  112.  
  113. FORMST(lpszNextPoint,"Next point:\0")
  114.  
  115. RDATA P1Req =
  116.    { sizeof(RDATA), RD_2DC, NULL, RDF_C, (DWORD*)&BuildL.Line.p1,
  117.     (DWORD*)&lpszFirstPoint, RDC_XH, MYLINE2, NULL, NULL, 0, NULL, 0};
  118.  
  119. RDATA P2Req =
  120.    { sizeof(RDATA), RD_2DC, NULL, RDF_C, (DWORD*)&BuildL.Line.p2,
  121.     (DWORD*)&lpszNextPoint, RDC_RBAND, MYLINE3, NULL, NULL, 0, NULL, 0};
  122.  
  123. ////////////////////////////////////////////////////////////////////////////
  124.  
  125.  
  126. ////////////////////////////////////////////////////////////////////////////
  127. void XPCALL MYLINE (void)
  128. {
  129.     ReqData(&P1Req);  //get first point
  130. }
  131. ////////////////////////////////////////////////////////////////////////////
  132.  
  133.  
  134. ////////////////////////////////////////////////////////////////////////////
  135. void XPCALL MYLINE2 (int Result,int Result2,int Result3)
  136. {
  137.     if (Result != X_OK) { CmdEnd(); return; } //If we did not get valid info
  138.     
  139.     MarkUndoAdd();  //Enable Undo
  140.     NewCsrOrg(BuildL.Line.p1.x, BuildL.Line.p1.y);  //Set cursor origin
  141.     ReqData(&P2Req);  //Get next point
  142. }
  143. ////////////////////////////////////////////////////////////////////////////
  144.  
  145.  
  146. ////////////////////////////////////////////////////////////////////////////
  147. void XPCALL MYLINE3 (int Result,int Result2,int Result3)
  148. {
  149.     pENTREC pEntRec;  //Create a new drawing database entity
  150.  
  151.     if (Result != X_OK) { CmdEnd(); return; } //If we did not get valid info
  152.  
  153.     //This gets all the current stuff like current color and assigns it
  154.     //to our line entity.
  155.     GetCStuff(&BuildL.CStuff);
  156.  
  157.     //Append a copy of our line to the DB and return a pointer to the new
  158.     //entity in the database.  We assign it to pEntRec.
  159.     pEntRec=DLApnd(NULL,(pENTREC)&BuildL);
  160.     
  161.     EDraw(pEntRec);  //Draw our line
  162.     ShowChanges(); //Needed for CC3 to "Show Changes" to the DB
  163.  
  164.     BuildL.Line.p1.x=BuildL.Line.p2.x;  //move 2nd x to 1st x
  165.     BuildL.Line.p1.y=BuildL.Line.p2.y;  //move 2nd y to 1st y
  166.     
  167.     NewCsrOrg(BuildL.Line.p1.x,BuildL.Line.p1.y);  //Set cursor origin
  168.  
  169.     ReqData(&P2Req);  //Get next point
  170. }
  171. ////////////////////////////////////////////////////////////////////////////
  172.  
  173.  
  174. ////////////////// DllMain - XP initialization & Unload code ///////////
  175. BOOL WINAPI DllMain (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
  176. {
  177.     switch (dwReason)
  178.     {
  179.         case DLL_PROCESS_ATTACH:
  180.         {
  181.             MyXP.ModHdl=hDLL;
  182.             XPRegCmd(&MyXP);
  183.             break;
  184.         }
  185.         case DLL_PROCESS_DETACH:
  186.         {
  187.             XPUnregCmd(&MyXP);
  188.             break;
  189.         }
  190.     }
  191.     return TRUE;
  192. }
  193. ////////////////////////////////////////////////////////////////////////////

Tuesday, August 3, 2010

Ok, you requested data, now what did you get?

Ok, so you have messed around with the RDATA function and you want to select an element, like a circle.  So you create a new RDATA function with the fallowing parameters:
  • Cursor:  RDC_PICK
  • Flags:  RDF_C
  • Type:  RD_Pick1
 Now create a data store variable of type pEntRec.

A pEntRec type is a very important type in writing XP's.  The pEntRec type is the generic entity pointer that can point to any type of drawing entity.  So, if you selected a circle with your RDATA call, pEntRec will point to the the circle entry within the "Drawing Database"

The "Drawing Database" is the collection of all the visible elements in the map.  When we select elements, Campaign Cartographer returns a pointer to the selected element in the "Drawing Database".

Once you get a pointer to an entity, you can edit, interrogate or delete it.

Every entity shares some common data - appropriately named: CSTUFF

CStuff is defined as:
Code Snippet - CSTUFF
  1. typedef struct
  2. {
  3.     int              ERLen;        // entity record length
  4.     unsigned char    EType;        // entity type code
  5.     char             EFlags;       // erase/select bits
  6.     char             EFlags2;      // extra flags
  7.     char             EColor;       // entity color
  8.     char             EColor2;      // fill (2nd) color
  9.     char             EThick;       // pen thickness 0..25.4 mm
  10.     short            WPlane;       // workplane (0 = XY plane)
  11.     short            ELayer;       // layer
  12.     short            ELStyle;      // line style (0=solid)
  13.     short            GroupID;      // group id (0 = not grouped)
  14.     short            EFStyle;      // fill style (0=hollow)
  15.     float            LWidth;       // line width
  16.     int              Tag;          // entity tag id
  17. } CSTUFF;


For example you have a pEntRec named pENTREC and you want to get the entities color:
pENTREC->CStuff.EColor

Saturday, July 24, 2010

Part 2 of a Campaign Cartographer 3 XP - Or, the best part for last.

In a previous post, I discussed how an XP is in three distinct parts. This post is about the second part: Where the rubber meets the road.

Because this is the main part of the XP, describing this section in any detail would be like trying to describing how any a website works by describing how HTML works.

But, there are two main functions of every XP. Input and Communicating with the user.

Input: Feeding data from you map into your XP is a major function. If you cannot select elements or locations (points), you cannot have much of an impact on your drawing.

Communication: Having the ability to select something without telling the user what your command is expecting the user to do.

These two functions are accomplished using RDATA (Request Data) and FORMST (Format String). Using these two together, we can prompt the user, with FORMST, that we currently want a type of data (a string, a point, an element etc.) and with RDATA, we are informing CC3 that it needs to let the user collect the data we want.

Here is a simple example that implements an XP command to enter a text word:

Code Snippet: XP dll Part #2
  1. void XPCALL GotText(int Result, int Result1, int Result2);
  2.  
  3. FORMST(lpszGetText, "Enter Text:")
  4.  
  5. RDATA rGetText = {sizeof(RDATA), RD_TxWord, NULL, RDF_C, (DWORD *)Text,
  6. (DWORD *)&lpszGetText, RDC_NONE, GotText, NULL, NULL, 0, NULL, 0};
  7.  
  8. void XPCALL GetText(void)
  9. {
  10.     ReqData(&rGetText);
  11. }
  12.  
  13. void XPCALL GotText(int Result, int Result1, int Result2)
  14. {
  15.     if(Result==X_OK)
  16.     {
  17.     }
  18.     else
  19.     {
  20.         CmdEnd();
  21.     }
  22. }

Line 1: This is the prototype for the method that RDATA will call when it has gotten a hold of the data you requested.

Line 3: This is the FORMST declaration. This declaration creates a variable (lpszGetText) and assigns it the text "Enter Text:".  True, we could have created a string with this text, but RDATA is expecting a FORMST variable.  Plus, when we get further into the power of FORMST, you will see that is it not merely a replacement for a char array.

Lines 5 & 6:  This is the RDATA declaration.  When you learn to read it, you will see that it says it is going to go get a "Word" (a string of characters without whitespace), store it in the char array Text (not declared in the code snippet), diplaying no visible cursor, displaying the text in the FORMST as our prompt and calling our method, GotText, when the user is done entering the request.

Line 8 - 11: This is our commands original method.  It would have been declared and prototyped in Part 1 of the XP code.

Line 10:  This is the call that invokes our RDATA.

Line 13 - 22: This is our method that RDATA will call when it has gotten a hold of the data you requested.

Line 15:  This line shows that the Result parameter contains the "Status" of the request we made.  If it is "OK" then we can further process what we got.

Line 20:  CmdEnd() is the XP command that notifies CC3 that our XP function has come to an end.

Tuesday, July 20, 2010

Part 3 of a Campaign Cartographer 3 XP

In a previous post, I discussed how an XP is in three distinct parts. This post is about the third part: XP DllMain Boilerplate.

Here is the third part (right below your usings) of an Example XP dll. Remember that this is the very last part of a XP dll:

Code Snippet: XP dll Part #3
  1. #pragma unmanaged
  2. BOOL WINAPI DllMain (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
  3. {
  4.     switch (dwReason)
  5.     {
  6.         case DLL_PROCESS_ATTACH:
  7.         {
  8.             MyXP.ModHdl=hDLL;
  9.             XPRegCmd(&MyXP);
  10.             break;
  11.         }
  12.         case DLL_PROCESS_DETACH:
  13.         {
  14.             XPUnregCmd(&MyXP);
  15.             break;
  16.         }
  17.     }
  18.     return TRUE;
  19. }

In truth, there is only one part that is optional. That is Line 1.

Line 1: This line is only needed if you are mixing standard C/C++ with .net code.

Otherwise, just copy it all at the bottom of the file and it will just work.

Sunday, July 18, 2010

Part 1 of a Campaign Cartographer 3 XP

In the last post, I discussed how an XP is in three distinct parts. This post is about the first part: XP Command declarations.

Here is the first part (right below your usings) of an Example XP dll:

Code Snippet: XP dll Part #1
  1. #define XPID 0xF000
  2.  
  3. void XPCALL About(void);
  4. void XPCALL Example1(void);
  5. void XPCALL Example2(void);
  6.  
  7. char CList[]="EX1\0EX2\0\0";
  8. PCMDPROC PList[]={About, Example1, Example2};
  9.  
  10. XP MyXP = { 0, CList, PList, 0, 0, 0, XPID, 0, 500, 0, 0, 500 };


Line 1: This defines the Xp ID. In the official documentation from Evolution Computing, the description is:

If your XP is going to be a commercial product, you will need to obtain an XPID number from Evolution Computing.

XPID numbers are unique XP module identifiers that allow FastCAD to identify which XPs should provide support for which custom entities and tools. That is their only purpose. Numbers in the range 0xF000-0xFFFF are available for unregistered use, but will not guarantee uniqueness against other products available to customers.

The only requirement Evolution Computing makes to issue a registered XPID number is that we know the product name, the company providing support, and that the product using it is about to be released for commercial sale. Please do not ask us for a registered XPID until your product is ready for sale.

So, unless you are about ready to sell your new add-on, just use the default.

Lines 3 through 5: These are the prototypes for the entry functions into your new commands. The first one, About, is required. It is called by the XPCFG command. If you have never tried it, run it now.

Go ahead, I'll wait.

Did you run it? If you did, you saw a list of all the loaded XP's and if you selected one of those XP's and clicked the about button, you just ran the about function for that XP. Its a good practice to keep the message box that is displayed up-to-date with a list of the commands you coded in your XP.

You may have also noticed that all the prototypes, except for the name, are exactly the same. This is because when a XP command is activated, it is up to the command to retrieve any and all data it needs, so the prototype is just defining the activation function. Think of it as your commands' Main() function.

Lines 7 & 8: These lines define CList and PList. CList is a string that defines the commands you are exposing to CC3. In the above example those commands are: "EX1" and "EX2". PList is a list of procedure names.

Notice the first one is "About". Remember, "About" is required and it must be the first procedure in the list. The next two procedures are "Example1" and "Example2". These procedures are, because of CList, mapped to the CC3 commands "EX1" and "EX2. If CList listed them in the reverse order: "EX2\0EX1\0\0", then "EX2" would be mapped to "Example1". So just remember that they are order dependant and that the first command always matches with the first procedure AFTER "About", the second command always matches with the second procedure AFTER "About" ... "

And lastly Line 10 is some required boilerplate that takes CList and PList and exposes them to CC3.

Saturday, July 17, 2010

The anatomy of a basic Campaign Cartographer 3 XP

A traditional XP is written in Assembly. This is because CC3 and its predecessors were written in Assembly. But, for the last decade or more there has been a library file that allows XP development in C/C++.

An XP source code file is made up of three distinct parts.

Part 1: XP Command declarations. Here is where we place all the prototypes of the entrypoint functions of each XP command. We also add the names of each command into lists that CC3 loads. In other words, this is where CC3 looks to map a command same, like PATH, with a function to call when the user types in that command.

Part 2: This is the meat of the XP dll. This is where all the actual coding magic works.

Part 3: This is where the DllMain for the dll is declared. This boilerplate code hooks up the dll into CC3.

Thursday, July 15, 2010

All the different ways to extend CC3

There are many ways to extend Campaign Cartographer 3: Macros, XP's, Intercom and reading and writing the file format directly.

In the blog posts that follow, we will be focusing on the middle two. Macros are good for a little automation but it just is not robust enough to produce complex functionality. If you can read/write the file system you could create some amazing things but A: It is a binary file B: It is compressed. So, it is just not a good place to start when learning to extend CC3.

So that leaves XP's and Intercom. They both can do great things with CC3, but from radically different directions.

An XP is an actual extension to CC3. So, if you want to add functionality to CC3, you will program an XP.

Intercom is a communication system with CC3. So, if you have an application that you want to integrate with CC3, Intercom is the system for you.

XP's will be my primary focus, simply because of the closer integration with CC3, but I will intersperse posts on Intercom to liven things up.

Monday, July 12, 2010

Welcome to CC3 Developer!

Welcome,

This is the introduction to my new blog dedicated to Campaign Cartographer (CC3) by ProFantasy Add-in development.

ProFantasy has produced a wonderful Game World Cartography product. Not just a single program, but an entire suite of add-ins that allow the user to draw just about anything map related.

The learning curve is a little steep, but just like with Photoshop, once you learn it, you have access to vasts amount of power.

Now I've written some "get started developing CC3 (and its predecessor CC2)" tutorials in the past. These are long, hand holding, filled with pictures tutorials and I plan on creating more in the future, but here I want to do the smaller howto type of posts that are the hallmark of blogging.

There is a vast API exposed for making CC3 Add-ins (also known as XP's) but, it is very lightly documented with zero examples. As I blog, I hope to expand the documentation of the API.

There are also other ways to automate and expand CC3. We will be looking at those from time to time as well.

So, sit back and enjoy the ride, as we delve into the world of CC3 programming!