RealDWG

07/23/2015

Exploding an AEC entity such as a Wall in AutoCAD results in a block reference and the block table record that it refers to contains faces. But when exploding the Wall entity in a RealDWG application results in a block reference and the block table record that it refers to contains lines.

In this blog post we will look into the reason for this difference and a way to workaround it. The workaround was provided by my colleagues Mikako Harada and Tony Zou. Many thanks to them.

While RealDWG can read a drawing created by any of the AutoCAD verticals, the object enablers specific to the verticals will still be needed for the host application to recognize the entities in that drawing. The object enabler defines the resulting entity set when the entity is exploded. As object enabler provides the explode functionality for its custom entities, they may also have other considerations.

For example, when an AEC drawing has its viewing direction set as Top View, a wall appears as a rectangle. When the viewing direction is any other view, the wall appears as a collection of faces.

Also, AEC drawings do not fully load into a side database when using the readDwgFile. This is a known behavior in AutoCAD Architecture and here is a blog post by my colleague Adam Nagy. For the database to get fully loaded in a side database it is required to call AecAppDbx::drawingPromoterAndIniter method.

Considering the above reasons, here are the C++ and .Net sample codes to explode a wall entity in a RealDWg application. The methods to have the database initialized and to set its viewing direction are invoked by accessing the exported methods from AecBase.dbx using their ordinal numbers. Please note that the ordinal numbers can change and are version specific. In the below code, the ordinal numbers for 2015 and 2016 releases are provided. For any other version, you may need to find them out using dumpbin on AecBase.lib from the \Lib-x64 folder.

04/20/2015

In this blog post, we will look at a few common setup related issues that can cause problems in running your RealDWG application.

System.IO.FileNotFoundException : This exception get thrown usually because your RealDwg Application does not find the dependent dlls. To ensure your RealDwg application accesses the right DLLs is to create an installer for your application with all the dependencies and then installing your application in a clean test machine and test it. But, for testing purposes during development, you can place your RealDWG application in the RealDWG root folder and test it. It is easier to simply set the build output path in the Visual Studio solution to place the output in the RealDWG root folder.

Also, ensure that the [RealDWG root path], [RealDWG root path]\Fonts and [RealDWG root path]\Support folder paths are added to the system's PATH environment variable. You may need to restart Visual Studio if you made the environment variable change when Visual Studio was open.

System.InvalidProgramException : RealDWG SDK has two versions of acdbmgd.dll just as the ObjectARX SDK does. The dll in RealDWG 2016\Inc folder has its executable code removed and the one in the RealDWG root folder is the unmodified dll. When referencing the "acdbmgd.dll" in your VisualStudio project from the Inc folder, remember to set "Copy Local" property to false for the reference. This is to prevent Visual Studio from replacing the "acdbmgd.dll" in the RealDWG root folder with the one found in "Inc" folder. If this happens, your application can throw a System.InvalidProgramException and the only way to fix it would be to find the "acdbmgd.dll" from the RealDWG install CD and copy it to the RealDWG root folder.

12/16/2014

When implementing an AutoCAD plugin, its quite easy to retrieve entity properties without having to directly deal with the entity's COM wrapper. But when implementing a RealDWG host application, this can become necessary to retrieve some of the entity properties.

If you are using .Net languages, using reflection can come very handy. Here is a blog post that can help :

If you are using C++, here are the changes to the DumpDwg sample from the RealDWG SDK. In this code snippet, our objective is to retrieve properties of a custom entity using the IDispatch interface of its COM wrapper. For example, a drawing that includes a "AsdkPoly" entity from "ObjectARX 2015\samples\entity\polysampPolySamp".

// Info on each IDispatch member item

struct stringdispid

{

CComBSTR bstr;

int nLen;

DISPID id;

};

// DispId map to retrieve properties

static stringdispid* m_pMap;

staticint m_nMapLen;

staticint m_nCount;

// Helper method to convert VARIANT to ads_point

static HRESULT get_PointFromVariant(

VARIANT &variant,

ads_point &pt)

{

if (V_VT(&variant) != VT_EMPTY)

{

if (V_VT(&variant) == (VT_ARRAY | VT_R8))

{

SAFEARRAY *psa = variant.parray;

long lStartIndex = 0;

long lEndIndex = 0;

SafeArrayGetLBound(psa, 1, &lStartIndex);

SafeArrayGetUBound(psa, 1, &lEndIndex);

if (lEndIndex == 2)

{

AcAxPoint3d tmpPt(variant);

pt[X] = tmpPt[X];

pt[Y] = tmpPt[Y];

pt[Z] = tmpPt[Z];

}

if (lEndIndex == 1)

{

AcAxPoint2d tmpPt(variant);

pt[X] = tmpPt[X];

pt[Y] = tmpPt[Y];

pt[Z] = 0.0;

}

return S_OK;

}

}

return E_INVALIDARG;

}

// Helper method to get the COM wrapper for an entity

static IAcadBaseObject* get_com_wrapper

(AcDbEntity* entity)

{

AcAxOleLinkManager* manager = AcAxGetOleLinkManager();

if (!manager)

return NULL;

IUnknown* unknown = manager->GetIUnknown(entity);

if (!unknown)

{

CLSID class_id;

if (Acad::eOk != entity->getClassID(&class_id))

return NULL;

HRESULT res = CoCreateInstance(

class_id,

NULL,

CLSCTX_ALL,

IID_IUnknown,

(LPVOID*) &unknown);

if (FAILED(res))

return NULL;

IAcadBaseObject* base = NULL;

res = unknown->QueryInterface(

__uuidof (IAcadBaseObject),

(VOID**) &base);

if (FAILED(res))

return NULL;

if (!manager->SetIUnknown(entity, unknown))

return FALSE;

base->SetObjectId(entity->objectId());

base->Release();

}

// Caller will release this interface

IAcadBaseObject* wrapper = NULL;

HRESULT res = unknown->QueryInterface(

__uuidof (IAcadBaseObject),

(VOID**) &wrapper);

if (FAILED(res))

return NULL;

return wrapper;

}

// Helper method to retrieve the function desc

// from the typeinfo and store it in a map

static HRESULT LoadNameCache(ITypeInfo* pTypeInfo)

{

TYPEATTR* pta;

HRESULT hr = pTypeInfo->GetTypeAttr(&pta);

if (SUCCEEDED(hr))

{

m_nCount = 0;

m_nMapLen = pta->cFuncs;

m_pMap = NULL;

if (m_nMapLen > 0)

m_pMap = new stringdispid[m_nMapLen];

for (int i=0; i<m_nMapLen; i++)

{

FUNCDESC* pfd;

if (SUCCEEDED(pTypeInfo->GetFuncDesc(i, &pfd)))

{

CComBSTR bstrName;

if (SUCCEEDED(pTypeInfo->GetDocumentation(

pfd->memid,

&bstrName,

NULL,

NULL,

NULL)))

{

if (pfd->invkind == DISPATCH_PROPERTYGET)

{

m_pMap[m_nCount].bstr.

Attach(bstrName.Detach());

m_pMap[m_nCount].nLen

= SysStringLen(m_pMap[i].bstr);

m_pMap[m_nCount].id = pfd->memid;

m_nCount++;

}

}

pTypeInfo->ReleaseFuncDesc(pfd);

}

}

pTypeInfo->ReleaseTypeAttr(pta);

}

return S_OK;

}

// Retreive properties from an entity using the

// IDispatch interface of its COM wrapper

HRESULT dumPolyProps(AcDbEntity *pEntity)

{

::CoInitialize(NULL);

IAcadBaseObject *pAcadObj = get_com_wrapper(pEntity);

CComQIPtr<ITypeLib> pTypeLib(pAcadObj);

CComQIPtr<ITypeInfo> pTypeInfo(pAcadObj);

TCHAR full_app_file_name[512] = L"" ;

int nBufferLength = 512;

TCHAR* filePart;

DWORD result;

result = SearchPath(NULL, _T("asdkcompoly.dbx" ),

_T(".dbx" ), 512, full_app_file_name, &filePart);

if (result && result < (DWORD)nBufferLength)

{

HRESULT hr = LoadTypeLib

(full_app_file_name, &pTypeLib);

if (FAILED(hr))

return hr;

UINT iCount = pTypeLib->GetTypeInfoCount();

for (UINT i=0; i < iCount ; i++)

{

hr = pTypeLib->GetTypeInfo(i, &pTypeInfo);

if (FAILED(hr))

return hr;

LoadNameCache(pTypeInfo);

CComQIPtr<IDispatch> pDisp(pAcadObj);

// Retrieve the properties

for (int index = 0; index < m_nCount; index++)

{

OLECHAR *sMember = m_pMap[index].bstr;

DISPID dispId;

hr = pDisp->GetIDsOfNames(

IID_NULL,

&sMember,

1,

LOCALE_SYSTEM_DEFAULT,

&dispId);

if (SUCCEEDED(hr))

{

unsignedint puArgErr = 0;

VARIANT VarResult;

VariantInit(&VarResult);

EXCEPINFO pExcepInfo;

DISPPARAMS pParams;

memset(&pParams, 0, sizeof (DISPPARAMS));

pParams.cArgs = 0;

hr = pDisp->Invoke(

dispId,

IID_NULL,

LOCALE_USER_DEFAULT,

DISPATCH_PROPERTYGET,

&pParams,

&VarResult,

&pExcepInfo,

NULL);

if (V_VT(&VarResult)

== (VT_ARRAY | VT_R8))

{

// Convert to ads_point

ads_point pt;

get_PointFromVariant(VarResult, pt);

_print(_T("%s : %lf %lf %lf\\n" ),

sMember, pt[0], pt[1], pt[2]);

}

elseif (VarResult.vt == VT_DISPATCH)

{

_print(_T("%s : VT_DISPATCH\\n" ),

sMember);

//IDispatchPtr pDispatch

// = VarResult.pdispVal;

}

else

{

hr = VariantChangeType(

&VarResult,

&VarResult,

0,

VT_BSTR);

_print(_T("%s : %s\\n" ),

sMember,

VarResult.bstrVal);

}

VariantClear(&VarResult);

}

}

delete [] m_pMap;

m_pMap = NULL;

}

}

CoUninitialize();

return S_OK;

}

// Invoke it from the dumpEntity method

void dumpEntity(AcDbEntity *pEnt)

{

if (_tcscmp(p, _T("AsdkPoly" )) == 0)

dumPolyProps(pEnt);

// ... Rest of the code

}

Here is a screenshot of the retrieved property values (Click on it to enlarge) :

02/05/2014

Here is a sample project from RealDWG DevTV that has been modified to use WiX to deploy the application.

To try this, please download and install the WiX Toolset.

There are two batch files included in the sample project folder. These batch files run as "Pre-build event" when the project is built. Please ensure that the path of the VC++ re-distributables, Fonts and other RealDWG merge modules are correctly specified in the batch files.

The purpose of the batch files is to copy all the files needed for the packaging to a common folder named as "ForPackaging". The WiX setup project builds the MSI by using this folder path without having to look for files in different paths.

09/11/2013

One of my developers was trying to create a toolbar button which automatically setup some text styles/parameters. Everything was fine except that on some machines, the button would fail – the error was “Font file doesn’t exist”. He was using a very commonly used font file “arial.ttf”, indeed arial.ttf is the default font for the “Standard” AutoCAD style.

04/08/2013

However, when I place the same functionality/code in a RealDWG dll and call that from an exe, then in case of its installed version, the second time I call my function from the dll the RasterImageDef objects are only available as ProxyObjects.

Solution

The RuntimeSystem.Initialize() and Terminate() functions are only supposed to be called once within an application session.

The easiest solution is to have a single static/Shared instance of your DwgHost class in your RealDWG dll and its constructor and destructor is implemented the way shown in the below code.

This way even if you provide static/Shared public functions in your RealDWG dll that can be called from your other applications, the RealDWG system is already initialized and its Initialize and Terminate functions are only called once:

04/05/2013

My application runs fine on the development machine but the installed version crashes on a clean test machine.

If I only reference my version of HostApplicationServices inside a try-catch (Autodesk.AutoCAD.Runtime.Exception), then the installed version pops up this error:

So it seems that some components are missing. When I checked the sample that comes with the Introduction to RealDWG Programming DevTV presentation, that had the same problem.

Solution

The sample is written in VS 2005. When you migrate it to VS 2008, then for some reason Visual Studio does not automatically pull in the policy merge modules that come with the included merge modules (*.msm)

As you'll find in many articles on the net, most Visual Studio merge modules require the corresponding policy merge modules.

So just make sure that those are also added to your merge module or installer project:

03/07/2013

If you are a seasoned RealDWG developer, you will have probably noticed that the .NET version of the RealDWG SDK’s HostApplicationServices object does not implement all of the virtual functions that are exposed by its unmanaged ObjectDBX counterpart, AcDbHostApplicationServices.

For the most part, this is a good idea because it simplifies your RealDWG application immensely.

The problem is that sometimes, just sometimes, you really need the full power of the SDK. If you do need that power from .NET then you are going to need to implement a Mixed Mode RealDWG app – one that implements both an underlying unmanaged AcDbHostApplicationServices derived class and a managed HostApplicationServices class that feeds off of it.

So in this example, I simply want to override AcDbHostApplicationServices::fatalError() with my own implementation. This is because the default implementation simply calls Exit( 0 ) which in my case is bad because I want to process many DWG files without failure. If I did not override this function, my RealDWG app would simply exit out and not finish.

Here’s the RealDWG .NET code I want to use… Ok, it’s not scanning lots of DWG files but you get the idea… Remember, I want an exception to be thrown when RealDWG encounters a corrupted DWG file rather than calling Exit( 0 )…

ImportsAutodesk.AutoCAD.DatabaseServices

ImportsSystem.Windows.Forms

' by Fenton Webb, DevTech, Autodesk 30/04/2010

PublicClassSimpleSampleDBXEngine

SharedSubMain()

' init the undelying CLI C++ HostApplication

UsingappAsAdskDBXEngine = NewAdskDBXEngine

ForiAsInteger = 0 To 100

Try

' new the database making sure to dispose it once we are finished, using/end using