I would like to store some information to XData fields, so it could also be saved to AutoCad models. From the mdl API I have found functions to save and read XData but how can I delete or modify certain fields?
Lets say for example that I want to save an ID to an element and for some reason the ID changes so I need to change the ID in a model. I could not found any functions to modify existing XData. There is a function to delete the whole XData (I think) but how to delete only my own data binded to my application?
To parse out xdata you'd have to walk through each of DXF group codes stored in the xdata linkage, something like this:
if (mdlLinkage_extractXDataLinkage (&pXData, &numBytes, pElement) == SUCCESS { for (int count=0; pXData != NULL && count < numBytes; pXData += size, count += 2*sizeof(short) + 4 + size;) { switch (GetNextXDataGroupCode(size, &pXData)) { case 1005: // object handle/element ID break; ... } } }
Where you move your linkage pointer via:
short GetNextXDataGroupCode(short& xdataSize, /* <= size of xdata item */byte **ppXData /* <=> pointer to XData buffer */) { short xdataType;
memcpy (&xdataType, *ppXData, sizeof xdataType); *ppXData += sizeof (short);
memcpy (&xdataSize, *ppXData, sizeof (short)); *ppXData += 3 * sizeof (short);
return xdataType; }
You may find XDATA group codes definitions from Adesk website:
http://docs.autodesk.com/ACD/2011/ENU/filesDXF/WS1a9193826455f5ff18cb41610ec0a2e719-7943.htm
To delete your own xdata you can use the same iteration through entire linkage, copy all those until you stop at your own application name, then skip through until you hit next application name. Upon finishing the process, add back your changed xdata to the copy and replace the existing linkage on the element. There are a couple of mdlLinkage functions that can make the process easier but they are not currently published.
Unknown said:There are a couple of mdlLinkage functions that can make the process easier but they are not currently published
Tantalysing! Would Bentley Systems like to publish them? I'm in favour of eliminating magic code such as:
count += 2*sizeof(short) + 4 + size
Should we have to write opaque calculations like that in the 21st century? It's enough to drive one to C#.
Regards, Jon Summers LA Solutions
Our initial thought was that, if XDATA were anything useful it would be only on AutoCAD, as MicroStation certainly does not make use of it. MicroStation users may only need it for DWG round trip purpose. In rare occasions if someone ever needs it, he/she presumably knows XDATA perhaps because he/she has used it in his/her apps on AutoCAD, therefore he/she does not need much assistance for that to work on MicroStation.
If XDATA turns out to be something more useful than we initially thought, then we should publish these API's. Anyone else out there uses XDATA on MicroStation?
Thanks for the code Don.
I managed to write some working code according to your example. Could you tell if there are lots of mistakes?
ElementRef deleteXData(ElementRef elemRef){ int status = SUCCESS; Bentley::Ustn::ElemDescrSmartP eDSP(elemRef, ACTIVEMODEL); // buffer for extracted xdata byte * buffer = NULL; UInt32 bufferIndex = 0; UInt32 bufferSize = 0; status = mdlLinkage_extractXDataLinkage(&buffer, &bufferSize, &eDSP->el); if (status != SUCCESS) { // there may not be any linkage return elemRef; } // buffer in here UInt32 copyIndex = 0; byte * copyBuffer = new byte[bufferSize]; memset(copyBuffer, NULL, bufferSize); ElementID id; ElementID swappedId = _byteswap_uint64(applicationId); // don't write own data bool write = true; // when own xdata ends bool end = false; // for storing parsed values // command chars char c; // block's data size short size; // to copy buffer blocks byte * blockStart = NULL; UInt32 copyBlockSize = 0; while (bufferIndex < bufferSize) { blockStart = &buffer[bufferIndex]; switch (getNextXDataBlock(size, buffer, bufferIndex)) { case DWGXDATA_Application_Name: id = 0; memcpy(&id, &buffer[bufferIndex], size); if (id == swappedId) { // own data found, so stop writing write = false; } break; case DWGXDATA_ControlString: memcpy(&c, &buffer[bufferIndex], size); if (c == '}') { // start writing again end = true; } break; } if (write) { // write to copy buffer copyBlockSize = 4 * sizeof(short) + size; if (bufferSize - copyIndex >= copyBlockSize) { memcpy(©Buffer[copyIndex], blockStart, copyBlockSize); copyIndex += copyBlockSize; } } if (end) { write = true; } // increment buffer index bufferIndex += size; } // write new buffer if (copyIndex > 0) { status = mdlLinkage_setXDataLinkage(eDSP.GetPtr(), copyBuffer, copyIndex); } else { // if buffer is empty delete whole linkage status = mdlLinkage_deleteXDataLinkage(&eDSP->el); if (status) { status = SUCCESS; } } // rewrite to model UInt32 filePos = mdlElmdscr_rewrite(eDSP.GetPtrVal(), NULL, mdlElmdscr_getFilePos(eDSP.GetPtrVal())); // update reference ElementRef newElemRef = mdlModelRef_getElementRef(ACTIVEMODEL, filePos); if (status != SUCCESS) { printf("Delete Attribute failed (status %d)\n", status); } delete[] copyBuffer; free(buffer); return newElemRef;}short getNextXDataBlock(short & size, byte * buffer, UInt32 & index){ short type; short tempSize = 0; // get group type memcpy(&type, &buffer[index], sizeof(short)); index += sizeof(short); // get data blocks length memcpy(&tempSize, &buffer[index], sizeof(short)); // move over length and skip 2 following fields index += 3 * sizeof(short); size = tempSize; return type;}
Assuming your swappedId logic works correctly, I did not spot any obvious error. I'm sure you can verify the ID swap logic easily through your debugging.
The only suggestion I have is to not rely on group control string '}'. This is because that not all AutoCAD developers follow the best practice guidelines per Autodesk's DXF file format and the group control strings may not present in their XDATA. Most developers would put control strings in there, but not all do. The only reliable check is DWGXDATA_Application_Name(1001 presumably). You may want to use your regapp name as a switch to turn on/off your copy operation.