So i have an ARX application with mfc window. In the window there's a button which I press at and then I should a random choose entity in the .DWG file. My purpose is to then save this single selected entity as a R12 DXF file. But also it could be block, and most of the time it's going to be a block. I had an initial algorithm in my head that i would create a function that accepts AcDbEntity* and file path of the whatever string type. Then inside of this function i would create a new document, append an entity to the document save it using dxf out. Here's the code, but it crashes the autocad:
void CRmWindow::EntitySaveAsDxf(AcDbEntity* pEntity)
{
if (pEntity == nullptr) {
acutPrintf(_T("Invalid entity.\n"));
return;
}
AcDbDatabase* pNewDb = new AcDbDatabase(false, true);
// Now we need to open the appropriate container which is inside BlockTable
AcDbBlockTable* pBlockTable = nullptr;
pNewDb->getSymbolTable(pBlockTable, AcDb::kForRead);
// Inside BlockTable, open the ModelSpace
AcDbBlockTableRecord* pBlockTableRecord = nullptr;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);
// After get ModelSpace we can close the BlockTable
pBlockTable->close();
// Using ModelSpace pointer we can add entity
AcDbObjectId entityId;
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
pBlockTableRecord->appendAcDbEntity(entityId, pEntity);
pEntity->close();
}
// To finish the process we need to close ModelSpace and the entity
pBlockTableRecord->close();
acdbDxfOutAsR12(pNewDb, path_from_mfc); // path from mfc is a member of the CRmWindow
delete pNewDb;
}
So as I said it crashes the autocad. Is it possible the create such a function?
And also I have a silly question why the function for getting access to the block table is called getsymboltable? Shouldn't be called getblocktable?
void CRmWindow::EntitySaveAsDxf()
{
AcDbDatabase* pDb = new AcDbDatabase();
AcDbBlockTable* pBtbl;
pDb->getSymbolTable(pBtbl, AcDb::kForRead);
AcDbBlockTableRecord* pBtblRcd;
pBtbl->getAt(ACDB_MODEL_SPACE, pBtblRcd, AcDb::kForWrite);
pBtbl->close();
AcDbObjectId entityId;
AcDbEntity* pEntity = nullptr;
// selected_entity is an ads_name variable member as well as path_from_mfc
if (acdbGetObjectId(entityId, selected_entity) == Acad::eOk)
{
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
pBtblRcd->appendAcDbEntity(pEntity);
pEntity->close();
}
}
acdbDxfOutAsR12(pDb, path_from_mfc);
delete pDb;
}
then i came with this solution but it's still does not work. This is the way i obtained ads_name variable:
ads_point clicked_point;
ads_name entity_name;
if (acedEntSel(L"Select entity: ", entity_name, clicked_point) == RTNORM)
{
AcDbObjectId entityId;
if (acdbGetObjectId(entityId, entity_name) == Acad::eOk)
{
selected_entity[0] = entity_name[0];
selected_entity[1] = entity_name[1];
AcDbEntity* pEntity = nullptr;
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
insert_to_tree(pEntity);
#if defined(ROBOMAX_VERBOSE)
acutPrintf(_T("Selected entity type: %s\n"), pEntity->isA()->name());
#endif // defined(ROBOMAX_VERBOSE)
pEntity->close();
}
}
}
it's a piece taken from the function that chose an entity in the autocad itself by clicking on the button in the mfc window. Please help!
Solved! Go to Solution.
Solved by daniel_cadext. Go to Solution.
Solved by Alexander.Rivilis. Go to Solution.
Solved by tbrammer. Go to Solution.
You can't just append an entity that is alread resident in dbSource=selected_entity.database() to the blocktable of another database dbTarget=pDb.
Either use Deep Cloning or create a shallow clone of the entity, set its properties to match the target database and append the clone.
Deep cloning:
AcDbObjectId IdModelSpaceTarget = acdbSymUtil()->blockModelSpaceId(dbTarget);
AcDbObjectIdArray sourceIds;
sourceIds.append(pEntity->objectId());
pEntity->close();
AcDbIdMapping idMap;
AcDb::DuplicateRecordCloning drc = AcDb::kDrcReplace;
Acad::ErrorStatus es = dbSource->wblockCloneObjects(sourceIds, IdModelSpaceTarget, idMap, false);
This is the recommended safe way to copy the entity along with all objects it needs (like layers, linetypes or in case of a blockreference or dimensions referenced blocks) from the source to the target database.
Shallow cloning:
AcDbEntity *clonedEntity = AcDbEntity::cast(pEntity->clone());
clonedEntity->setDatabaseDefaults(pDb);
// append clonedEntity
You can only use shallow cloning if your entity is a "simple entity" that doesn't have references to other database objects (like blocks) that need to be copied along with it. The shallow clone will loose the standard properties like layer, linetype, material and so on and instead use the defaults of the target database.
Try this code as a sample:
static void RivilisDxfR12OutSelected() {
ads_name ss;
if (acedSSGet(NULL, NULL, NULL, NULL, ss) == RTNORM)
{
Adesk::Int32 len = 0; acedSSLength(ss, &len);
if (len > 0)
{
AcDbObjectIdArray ids; ids.setPhysicalLength(len);
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name en; AcDbObjectId id;
acedSSName(ss, i, en);
acdbGetObjectId(id, en);
ids.append(id);
}
//////////////////////////////////////////////////////////////////////////
// Create new base without structure
//////////////////////////////////////////////////////////////////////////
AcDbDatabase *tempDb = new AcDbDatabase(Adesk::kFalse);
Acad::ErrorStatus es;
if ((es = acdbCurDwg()->wblock(tempDb, ids, AcGePoint3d::kOrigin)) == Acad::eOk)
{
if ((es = acdbDxfOutAsR12(tempDb, L"C:\\test.dxf")) != Acad::eOk)
acutPrintf(L"\nacdbDxfOutAsR12(...) = %s", acadErrorStatusText(es));
}
else
{
acutPrintf(L"\nacdbCurDwg()->wblock(...) = %s", acadErrorStatusText(es));
}
delete tempDb;
}
}
}
Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"
Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
![]() | ![]() |
Thanks for your reply. I refactored my code correspondingly to your 'deep cloning' answer, and got something like this:
void CRmWindow::EntitySaveAsDxf(SaveDxfMode mode)
{
AcDbDatabase* dbSource = acdbHostApplicationServices()->workingDatabase();
switch (mode)
{
case SaveDxfMode::SELECTED_ENTITY:
{
AcDbDatabase* dbTarget = new AcDbDatabase();
AcDbObjectId entityId;
AcDbEntity* pEntity = nullptr;
// selected_entity is an ads_name variable member as well as path_from_mfc
if (acdbGetObjectId(entityId, selected_entity) == Acad::eOk)
{
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
AcDbObjectId IdModelSpaceTarget = acdbSymUtil()->blockModelSpaceId(dbTarget);
AcDbObjectIdArray sourceIds;
sourceIds.append(pEntity->objectId());
pEntity->close();
AcDbIdMapping idMap;
AcDb::DuplicateRecordCloning drc = AcDb::kDrcReplace;
Acad::ErrorStatus es = dbSource->wblockCloneObjects(sourceIds, IdModelSpaceTarget, idMap, drc, false);
}
}
acdbDxfOutAsR12(dbTarget, path_from_mfc);
delete dbTarget;
break;
}
case SaveDxfMode::THE_WHOLE_PROJECT:
{
acdbDxfOutAsR12(dbSource, path_from_mfc);
break;
}
default: { break; }
}
}
I've been testing this piece of code in both modes on .DWG file that have complex BlockReference object and simple circle. I selected a circle, then .DXF file got generated. In the second try I selected BlockReference, and also .DXF file got generated in a different location so I don't overwrite the first .DXF file. I've compared two of these files and they're absolutely the same with 1070 lines. Then I've decided to switch the mode of my function to SaveDxfMode::THE_WHOLE_PROJECT and .DXF file got generate with 41692. So something is telling that .DXF file that has been generated after I selected BlockReference should've been something close 40k lines. I going to attach all of the .DXF files that has been generated and also my .DWG with both things as well. Do you what the problem is? Why do files the same for both circle and block? Thanks a lot in advance!
Both dxf-files have not any entities (not AcDbCircle, not AcDbBlockReference). So wblockCloneObjects(...) return error code.
Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"
Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
![]() | ![]() |
Yes you're right. I also have tested your way of doing this and got completely the same mistake:
Command: ROBOMAX
Command:
Select objects: 1 found
Select objects:
acdbCurDwg()->wblock(...) = eLockViolation
Do you have any assumptions on why this is a case here?
It is look like your CRmWindow is a modeless window/dialog. So you have to lock database. Try to add in the beginning of your method such line:
AcAxDocLock doclock(acdbCurDwg());
Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"
Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
![]() | ![]() |
Yes, you're right on spot! Thanks a lot! But since you've provided a your solution I have got an idea to add a feature in my application to save several selected entities as a .DXF file. So again I have a class where I have my ids array. And this is the saves entities as a DXF:
case SaveDxfMode::SELECTED_ENTITIES:
{
AcDbDatabase* tempDb = new AcDbDatabase(Adesk::kFalse);
Acad::ErrorStatus es;
if ((es = dbSource->wblock(tempDb, ids, AcGePoint3d::kOrigin)) == Acad::eOk)
{
if ((es = acdbDxfOutAsR12(tempDb, path_from_mfc)) != Acad::eOk)
acutPrintf(L"\nacdbDxfOutAsR12(...) = %s", acadErrorStatusText(es));
}
else
{
acutPrintf(L"\nacdbCurDwg()->wblock(...) = %s", acadErrorStatusText(es));
}
delete tempDb;
ids.removeAll();
}
And this my class where I have my array:
class CRmWindow : public CAdUiBaseDialog {
//....
//....
// OR
AcDbObjectIdArray ids; // ids of the entities we select from the mfc window
//...
};
and this is how do I select entities (your solution):
void CRmWindow::OnBnClickedButtonSelectEntities()
{
save_instruction = SaveDxfMode::SELECTED_ENTITIES;
if (!IsIconic())
{
ShowWindow(SW_MINIMIZE);
}
m_treeCtrl.DeleteAllItems();
folder_path_entry.SetWindowTextW(TEXT(""));
ads_name ss;
if (acedSSGet(NULL, NULL, NULL, NULL, ss) == RTNORM)
{
Adesk::Int32 len = 0;
acedSSLength(ss, &len);
if (len > 0)
{
ids.setPhysicalLength(len);
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name en;
AcDbObjectId id;
acedSSName(ss, i, en);
acdbGetObjectId(id, en);
ids.append(id);
if (acdbGetObjectId(id, en) == Acad::eOk)
{
AcDbEntity* pEntity = nullptr;
if (acdbOpenObject(pEntity, id, AcDb::kForRead) == Acad::eOk)
{
insert_to_tree(pEntity);
pEntity->close();
}
}
}
}
}
if (IsIconic())
{
ShowWindow(SW_RESTORE);
}
}
But for some reason, even though in tree control only selected entities are appeared, entire project is being saved, in other words all of the entities from the .DWG file. Do you know what the problem is? Is it possible to save only selected entities from acedSSGet? Thanks in advance!
Before acedSSGet(...):
BeginEditorCommand();
After acedSSGet(...):
CompleteEditorCommand();
Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"
Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
![]() | ![]() |
BeginEditorCommand();
if (acedSSGet(NULL, NULL, NULL, NULL, ss) == RTNORM)
{
CompleteEditorCommand();
Adesk::Int32 len = 0;
acedSSLength(ss, &len);
if (len > 0)
{
ids.setPhysicalLength(len);
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name en;
AcDbObjectId id;
acedSSName(ss, i, en);
acdbGetObjectId(id, en);
ids.append(id);
if (acdbGetObjectId(id, en) == Acad::eOk)
{
AcDbEntity* pEntity = nullptr;
if (acdbOpenObject(pEntity, id, AcDb::kForRead) == Acad::eOk)
{
insert_to_tree(pEntity);
pEntity->close();
}
}
}
}
}
else
{
CancelEditorCommand();
}
I modified code like this and it still saves the whole file 😞
I see at least 1 error. Once AcDbEntity is closed, it cannot be used. And besides, I don't see the full code. So I don't know what's going on. I only know that if AcDbBlockReference is selected, then AcDbBlockTableRecord will also be included in the dxf-file and, accordingly, the file size will be much larger than you think.
Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"
Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
![]() | ![]() |
Okay so Im going to get you through my code. When I load an ARX application the MFC window appears that is created using this code:
static int ads_select_entity_dialog()
{
CRmWindow* pDialog = nullptr;
// For further portability
if (pDialog == nullptr)
{
pDialog = new CRmWindow;
if (!pDialog->Create(CRmWindow::IDD, acedGetAcadFrame()))
{
delete pDialog;
pDialog = nullptr;
AfxMessageBox(L"Error while creating Dialog window %ld");
return RTERROR;
}
else
{
if (!pDialog->IsWindowVisible())
{
pDialog->ShowWindow(SW_SHOW);
}
if (pDialog->IsIconic())
{
pDialog->ShowWindow(SW_RESTORE);
}
}
}
return RTNORM;
}
The image of the window:
Then if I would click on the Select entities button this function is going to be called:
void CRmWindow::OnBnClickedButtonSelectEntities()
{
save_instruction = SaveDxfMode::SELECTED_ENTITIES;
if (!IsIconic())
{
ShowWindow(SW_MINIMIZE);
}
m_treeCtrl.DeleteAllItems();
folder_path_entry.SetWindowTextW(TEXT(""));
ads_name ss;
BeginEditorCommand();
if (acedSSGet(NULL, NULL, NULL, NULL, ss) == RTNORM)
{
CompleteEditorCommand();
Adesk::Int32 len = 0;
acedSSLength(ss, &len);
if (len > 0)
{
ids.setPhysicalLength(len);
for (Adesk::Int32 i = 0; i < len; i++)
{
ads_name en;
AcDbObjectId id;
acedSSName(ss, i, en);
acdbGetObjectId(id, en);
ids.append(id);
if (acdbGetObjectId(id, en) == Acad::eOk)
{
AcDbEntity* pEntity = nullptr;
if (acdbOpenObject(pEntity, id, AcDb::kForRead) == Acad::eOk)
{
insert_to_tree(pEntity);
pEntity->close();
}
}
}
}
}
else
{
CancelEditorCommand();
}
if (IsIconic())
{
ShowWindow(SW_RESTORE);
}
}
And this is ids in my window class:
class CRmWindow : public CAdUiBaseDialog {
DECLARE_DYNAMIC(CRmWindow)
////////////////
AcDbObjectIdArray ids; // ids of the entities we select from the mfc window
/////////////////
///////////////////////////////////////////////////////////////////////////////////
};
Then obviously after this function is called I prompted to select several entities in my .DWG file. So Im going to create .DWG file with three simple entities (Circle, Arc, Line) and only select two of them (Circle, Arc). When Im done selecting them I hit right-click and these entities appear in my mfc window like this:
Then Save DXF become available and when I press on this function Im prompted to select a path using CFolderPicker an this function is going to be called:
void CRmWindow::OnBnClickedSaveDxf()
{
if (m_treeCtrl.GetCount() == 0) {
save_instruction = SaveDxfMode::THE_WHOLE_PROJECT;
}
CString save_known_as;
switch (save_instruction)
{
case SaveDxfMode::SELECTED_ENTITIES:
{
save_known_as = "entities";
break;
}
case SaveDxfMode::SELECTED_ENTITY:
{
CString object_name;
AcDbObjectId entityId;
if (acdbGetObjectId(entityId, selected_entity) == Acad::eOk)
{
AcDbEntity* pEntity = nullptr;
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
CString new_name(reduced_name(pEntity).c_str());
save_known_as = new_name;
pEntity->close();
}
}
break;
}
case SaveDxfMode::THE_WHOLE_PROJECT:
{
save_known_as = "whole_dwg";
break;
}
default: { break; }
}
select_path_using_folder_picker(save_known_as);
SaveAsDxf();
}
And here's the function on the line 40:
void CRmWindow::SaveAsDxf()
{
AcAxDocLock doclock(acdbCurDwg()); // Lock the database to avoid eLockViolation because of the modeless mfc-window
AcDbDatabase* dbSource = acdbHostApplicationServices()->workingDatabase();
switch (save_instruction)
{
case SaveDxfMode::SELECTED_ENTITY:
{
AcDbDatabase* dbTarget = new AcDbDatabase();
AcDbObjectId entityId;
AcDbEntity* pEntity = nullptr;
// selected_entity is an ads_name variable member as well as path_from_mfc
if (acdbGetObjectId(entityId, selected_entity) == Acad::eOk)
{
if (acdbOpenObject(pEntity, entityId, AcDb::kForRead) == Acad::eOk)
{
AcDbObjectId IdModelSpaceTarget = acdbSymUtil()->blockModelSpaceId(dbTarget);
AcDbObjectIdArray sourceIds;
sourceIds.append(pEntity->objectId());
pEntity->close();
AcDbIdMapping idMap;
AcDb::DuplicateRecordCloning drc = AcDb::kDrcReplace;
Acad::ErrorStatus es = dbSource->wblockCloneObjects(sourceIds, IdModelSpaceTarget, idMap, drc, false);
if (es != Acad::eOk) {
const ACHAR* errMsg = acadErrorStatusText(es);
acutPrintf(_T("Error: %s\n"), errMsg);
}
}
}
acdbDxfOutAsR12(dbTarget, path_from_mfc);
delete dbTarget;
break;
}
case SaveDxfMode::SELECTED_ENTITIES:
{
// Put an address into the file tbh idk why.
std::wstring metadata_filename(path_from_mfc);
metadata_filename += L".metadata.txt";
std::wofstream metadata(metadata_filename);
if (metadata.is_open())
{
metadata << "Listing of the entities inside of the file: " << path_from_mfc << "\n";
for (int i = 0; i < ids.length(); i++)
{
AcDbObjectId objectId = ids.at(i);
AcDbEntity* entity = nullptr;
if (acdbOpenObject(entity, objectId, AcDb::kForRead) == Acad::eOk)
{
CString entityName = entity->isA()->name();
std::wstring ent_str_name(entityName);
metadata << "Entity name: " << ent_str_name << "\n";
entity->close();
}
}
metadata.close();
}
AcDbDatabase* tempDb = new AcDbDatabase(Adesk::kFalse);
Acad::ErrorStatus es;
if ((es = dbSource->wblock(tempDb, ids, AcGePoint3d::kOrigin)) == Acad::eOk)
{
if ((es = acdbDxfOutAsR12(tempDb, path_from_mfc)) != Acad::eOk)
acutPrintf(L"\nacdbDxfOutAsR12(...) = %s", acadErrorStatusText(es));
}
else
{
acutPrintf(L"\nacdbCurDwg()->wblock(...) = %s", acadErrorStatusText(es));
}
delete tempDb;
ids.removeAll();
}
case SaveDxfMode::THE_WHOLE_PROJECT:
{
acdbDxfOutAsR12(dbSource, path_from_mfc);
break;
}
default: { break; }
}
}
So it got saved as a DXF file successfully, but when I open this save .DXF file in the autocad, I see all of the entities, but expected to only ones that I've selected:
So this is the last problem that I have to solve in this project on the ARX side. If you could help me with this it would sincerely appreciated. I hope I'm not getting too impudent. Thanks in advance!
FYI, case SaveDxfMode::SELECTED_ENTITIES: in function SaveAsDxf, is missing a break:
Can't find what you're looking for? Ask the community or share your knowledge.