如何在 AutoCAD 2020 中同步来自模型 space 的视口?
How to sync viewports from model space in AutoCAD 2020?
问候各位专家,我正在尝试在两个windows AutoCAD 模型中同步移动和缩放,即在两个不同的平面打开.dwg 一分为二windows 一活动和另一个非活动同步缩放(滚动 + 或 -)或从活动 window 到非活动一个的移动(PAN)(在 AutoCAD M3D -> SYSWINDOWS :: tile Vertical 中具有两个开放平面的模型中),我正在研究,我发现这段代码可以做我想做的,但只能用一架飞机,问题是我不能让它在 VS2019 c ++ 中工作,我在“WinCallBack”行中得到一个错误,表明 BOOL 不能成为一个常量,我提前感谢你的帮助。
#include "StdAfx.h"
#include "resource.h"
#pragma warning( disable : 4278 )
#include <windows.h>
#include <stdio.h>
#include "acedCmdNF.h"
#include "AcMyEditorReactor.h"
#include "AcMyInputContextReactor.h"
// Viewchanged notification is not received during a pan or zoom using mouse wheel.
// So identify those using WM messages.
BOOL WinCallBack(MSG *pMsg)
{
if( pMsg->message == WM_VSCROLL ||
pMsg->message == WM_HSCROLL ||
pMsg->message == WM_MOUSEWHEEL ||
pMsg->message == WM_MBUTTONUP)
{
// Sync the modelspace viewports
acDocManager->sendStringToExecute(acDocManager->mdiActiveDocument(), ACRX_T("SyncVTR "), false, true, false);
}
return FALSE;
}
class CMyTest1App : public AcRxArxApp
{
private:
AcMyEditorReactor *pEditorReactor;
AcMyInputContextReactor *pInputContextReactor;
public:
CMyTest1App () : AcRxArxApp ()
{
}
virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;
// Editor reactor to receive to ViewChanged notification
pEditorReactor = new AcMyEditorReactor(true);
// InputContext reactor to receive quiescent state change notification
pInputContextReactor = new AcMyInputContextReactor();
// Viewchanged notification is not received during a pan or zoom using mouse wheel.
// So identify those using WM messages.
acedRegisterFilterWinMsg(WinCallBack);
return (retCode);
}
virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;
// cleanup
if(pEditorReactor)
{
delete pEditorReactor;
pEditorReactor = NULL;
}
if(pInputContextReactor)
{
delete pInputContextReactor;
pInputContextReactor = NULL;
}
acedRemoveFilterWinMsg(WinCallBack);
return (retCode);
}
virtual AcRx::AppRetCode On_kLoadDwgMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kLoadDwgMsg (pkt) ;
return (retCode) ;
}
virtual void RegisterServerComponents ()
{
}
// Command to sync the model space viewport parameters
static void AdskMyTestSyncVTR()
{
// Get the VTR updated
acedVports2VportTableRecords();
// We will update the other VTR only if view parameters change
Adesk::Boolean updateNeeded = Adesk::kFalse;
Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcApDocument *pDoc = acDocManager->document(pDb);
if( pDoc == NULL )
return;
es = acDocManager->lockDocument(pDoc);
// This code at present can only deal with 2 Modelspace viewports split vertically in half
if(pDb->tilemode() == Adesk::kFalse)
{
struct resbuf rb;
if(ads_getvar(_T("cvport"), &rb) != RTNORM)
{
acutPrintf(_T("\nError using ads_getvar().\n"));
return;
}
if(rb.resval.rint == 1)
{
return; // Can only work with model space viewports.
}
}
AcDbViewportTable *pVT = NULL;
pDb->getViewportTable(pVT,AcDb::kForRead);
// Identify the left and right modelspace viewports
AcDbViewportTableRecord *pLeftVTR = NULL;
AcDbViewportTableRecord *pRightVTR = NULL;
AcDbViewportTableIterator *pIter = NULL;
es = pVT->newIterator(pIter);
if(es == Acad::eOk)
{
for (;!pIter->done();pIter->step())
{
AcDbViewportTableRecord *pVTR = NULL;
es = pIter->getRecord(pVTR, AcDb::kForRead);
if(es == Acad::eOk)
{
AcGePoint2d ll = pVTR->lowerLeftCorner();
AcGePoint2d ur = pVTR->upperRightCorner();
if(ll.isEqualTo(AcGePoint2d(0, 0)))
{// Left modelspace viewport
pLeftVTR = pVTR;
}
else if(ur.isEqualTo(AcGePoint2d(1.0, 1.0)))
{// Right modelspace viewport
pRightVTR = pVTR;
}
else
pVTR->close();
}
}
// If for some reason, we did not have two modelspace viewports,
// lets stop here.
if(pLeftVTR == NULL)
{
if(pRightVTR != NULL)
pRightVTR->close();
return;
}
if(pRightVTR == NULL)
{
if(pLeftVTR != NULL)
pLeftVTR->close();
return;
}
// Ensure that the two viewports are split vertically in half.
// If not, the view parameters when applied from one to another
// may not apply directly using this code.
// If the viewports were resized manually, set them right.
AcGePoint2d ll1 = pLeftVTR->lowerLeftCorner();
AcGePoint2d ur1 = pLeftVTR->upperRightCorner();
AcGePoint2d ll2 = pRightVTR->lowerLeftCorner();
AcGePoint2d ur2 = pRightVTR->upperRightCorner();
if(ll1.isEqualTo(AcGePoint2d(0.0, 0.0)) == false)
{
if(! pLeftVTR->isWriteEnabled())
pLeftVTR->upgradeOpen();
pLeftVTR->setLowerLeftCorner(AcGePoint2d(0.0, 0.0));
}
if(ur1.isEqualTo(AcGePoint2d(0.5, 1.0)) == false)
{
if(! pLeftVTR->isWriteEnabled())
pLeftVTR->upgradeOpen();
pLeftVTR->setUpperRightCorner(AcGePoint2d(0.5, 1.0));
}
if(ll2.isEqualTo(AcGePoint2d(0.5, 0.0)) == false)
{
if(! pRightVTR->isWriteEnabled())
pRightVTR->upgradeOpen();
pRightVTR->setLowerLeftCorner(AcGePoint2d(0.5, 0.0));
}
if(ur2.isEqualTo(AcGePoint2d(1.0, 1.0)) == false)
{
if(! pRightVTR->isWriteEnabled())
pRightVTR->upgradeOpen();
pRightVTR->setUpperRightCorner(AcGePoint2d(1.0, 1.0));
}
// Get the active model space viewport
struct resbuf res;
acedGetVar(L"CVPORT", &res);
short vpnumber = res.resval.rint;
// Identify the model space viewports from/to which settings will be copied
// The active modelspace viewport is the viewport from which settings will be copied
AcDbViewportTableRecord *pFromVTR = NULL;
AcDbViewportTableRecord *pToVTR = NULL;
if(pLeftVTR->number() == vpnumber)
{
pFromVTR = pLeftVTR;
pToVTR = pRightVTR;
}
if(pRightVTR->number() == vpnumber)
{
pFromVTR = pRightVTR;
pToVTR = pLeftVTR;
}
// Sorry, we did not identify the active viewport
// from which settings need to be copied.
if(pFromVTR == NULL || pToVTR == NULL)
return;
// Copy the VTR settings from one modelspace viewport to another
// only if they are different. We will use a tolerance to ensure
// very small differences do not get us in a soup. I meant loop :)
AcGeTol newTol;
newTol.setEqualPoint (0.00001);
newTol.setEqualVector(0.00001);
// ViewDirection
AcGeVector3d fromViewDir = pFromVTR->viewDirection();
AcGeVector3d toViewDir = pToVTR->viewDirection();
if(pFromVTR->viewDirection().isEqualTo(pToVTR->viewDirection(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setViewDirection(pFromVTR->viewDirection());
updateNeeded = Adesk::kTrue;
}
// ViewTwist
if(abs(pFromVTR->viewTwist() - pToVTR->viewTwist()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setViewTwist(pFromVTR->viewTwist());
updateNeeded = Adesk::kTrue;
}
// Target
if(pFromVTR->target().isEqualTo(pToVTR->target(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setTarget(pFromVTR->target());
updateNeeded = Adesk::kTrue;
}
// BackClipEnabled
if(pFromVTR->backClipEnabled() != pToVTR->backClipEnabled())
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setBackClipEnabled(pFromVTR->backClipEnabled());
updateNeeded = Adesk::kTrue;
}
// BackClipDistance
if(abs(pFromVTR->backClipDistance() - pToVTR->backClipDistance()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setBackClipDistance(pFromVTR->backClipDistance());
updateNeeded = Adesk::kTrue;
}
// FrontClipEnabled
if(pFromVTR->frontClipEnabled() != pToVTR->frontClipEnabled())
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setFrontClipEnabled(pFromVTR->frontClipEnabled());
updateNeeded = Adesk::kTrue;
}
// FrontClipDistance
if(abs(pFromVTR->frontClipDistance() - pToVTR->frontClipDistance()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setFrontClipDistance(pFromVTR->frontClipDistance());
updateNeeded = Adesk::kTrue;
}
// Elevation
if(abs(pFromVTR->elevation() - pToVTR->elevation()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setElevation(pFromVTR->elevation());
updateNeeded = Adesk::kTrue;
}
// centerPoint
if(pFromVTR->centerPoint().isEqualTo(pToVTR->centerPoint(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setCenterPoint(pFromVTR->centerPoint());
updateNeeded = Adesk::kTrue;
}
// Height
if(abs(pFromVTR->height() - pToVTR->height()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setHeight(pFromVTR->height());
updateNeeded = Adesk::kTrue;
}
// Width
if(abs(pFromVTR->width() - pToVTR->width()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setWidth(pFromVTR->width());
updateNeeded = Adesk::kTrue;
}
// Done with the VTR
pLeftVTR->close();
pRightVTR->close();
delete pIter;
}
es = pVT->close();
es = acDocManager->unlockDocument(pDoc);
// Update the Vports if we did change any of the VTR parameters
if(updateNeeded)
{
acedVportTableRecords2Vports();
}
}
};
//-----------------------------------------------------------------------------
IMPLEMENT_ARX_ENTRYPOINT(CMyTest1App)
ACED_ARXCOMMAND_ENTRY_AUTO(CMyTest1App, AdskMyTest, SyncVTR, SyncVTR, ACRX_CMD_MODAL, NULL)
https://adndevblog.typepad.com/autocad/2014/07/synchronizing-model-space-viewports.html
我找到了两个解决问题的应用程序,DWGsync(在 autocad 2021 之前免费且兼容)https://apps.autodesk.com/ACD/es/Detail/Index?id=2788892389049910944&appLang=en&os=Win32_64 and Drawing Sync (licensed and compatible autocad 2018) https://apps.autodesk.com/ACD/en/Detail/Index?id=2152736212918385179&appLang=en&os=Win32_64
问候各位专家,我正在尝试在两个windows AutoCAD 模型中同步移动和缩放,即在两个不同的平面打开.dwg 一分为二windows 一活动和另一个非活动同步缩放(滚动 + 或 -)或从活动 window 到非活动一个的移动(PAN)(在 AutoCAD M3D -> SYSWINDOWS :: tile Vertical 中具有两个开放平面的模型中),我正在研究,我发现这段代码可以做我想做的,但只能用一架飞机,问题是我不能让它在 VS2019 c ++ 中工作,我在“WinCallBack”行中得到一个错误,表明 BOOL 不能成为一个常量,我提前感谢你的帮助。
#include "StdAfx.h"
#include "resource.h"
#pragma warning( disable : 4278 )
#include <windows.h>
#include <stdio.h>
#include "acedCmdNF.h"
#include "AcMyEditorReactor.h"
#include "AcMyInputContextReactor.h"
// Viewchanged notification is not received during a pan or zoom using mouse wheel.
// So identify those using WM messages.
BOOL WinCallBack(MSG *pMsg)
{
if( pMsg->message == WM_VSCROLL ||
pMsg->message == WM_HSCROLL ||
pMsg->message == WM_MOUSEWHEEL ||
pMsg->message == WM_MBUTTONUP)
{
// Sync the modelspace viewports
acDocManager->sendStringToExecute(acDocManager->mdiActiveDocument(), ACRX_T("SyncVTR "), false, true, false);
}
return FALSE;
}
class CMyTest1App : public AcRxArxApp
{
private:
AcMyEditorReactor *pEditorReactor;
AcMyInputContextReactor *pInputContextReactor;
public:
CMyTest1App () : AcRxArxApp ()
{
}
virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;
// Editor reactor to receive to ViewChanged notification
pEditorReactor = new AcMyEditorReactor(true);
// InputContext reactor to receive quiescent state change notification
pInputContextReactor = new AcMyInputContextReactor();
// Viewchanged notification is not received during a pan or zoom using mouse wheel.
// So identify those using WM messages.
acedRegisterFilterWinMsg(WinCallBack);
return (retCode);
}
virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;
// cleanup
if(pEditorReactor)
{
delete pEditorReactor;
pEditorReactor = NULL;
}
if(pInputContextReactor)
{
delete pInputContextReactor;
pInputContextReactor = NULL;
}
acedRemoveFilterWinMsg(WinCallBack);
return (retCode);
}
virtual AcRx::AppRetCode On_kLoadDwgMsg (void *pkt)
{
AcRx::AppRetCode retCode =AcRxArxApp::On_kLoadDwgMsg (pkt) ;
return (retCode) ;
}
virtual void RegisterServerComponents ()
{
}
// Command to sync the model space viewport parameters
static void AdskMyTestSyncVTR()
{
// Get the VTR updated
acedVports2VportTableRecords();
// We will update the other VTR only if view parameters change
Adesk::Boolean updateNeeded = Adesk::kFalse;
Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcApDocument *pDoc = acDocManager->document(pDb);
if( pDoc == NULL )
return;
es = acDocManager->lockDocument(pDoc);
// This code at present can only deal with 2 Modelspace viewports split vertically in half
if(pDb->tilemode() == Adesk::kFalse)
{
struct resbuf rb;
if(ads_getvar(_T("cvport"), &rb) != RTNORM)
{
acutPrintf(_T("\nError using ads_getvar().\n"));
return;
}
if(rb.resval.rint == 1)
{
return; // Can only work with model space viewports.
}
}
AcDbViewportTable *pVT = NULL;
pDb->getViewportTable(pVT,AcDb::kForRead);
// Identify the left and right modelspace viewports
AcDbViewportTableRecord *pLeftVTR = NULL;
AcDbViewportTableRecord *pRightVTR = NULL;
AcDbViewportTableIterator *pIter = NULL;
es = pVT->newIterator(pIter);
if(es == Acad::eOk)
{
for (;!pIter->done();pIter->step())
{
AcDbViewportTableRecord *pVTR = NULL;
es = pIter->getRecord(pVTR, AcDb::kForRead);
if(es == Acad::eOk)
{
AcGePoint2d ll = pVTR->lowerLeftCorner();
AcGePoint2d ur = pVTR->upperRightCorner();
if(ll.isEqualTo(AcGePoint2d(0, 0)))
{// Left modelspace viewport
pLeftVTR = pVTR;
}
else if(ur.isEqualTo(AcGePoint2d(1.0, 1.0)))
{// Right modelspace viewport
pRightVTR = pVTR;
}
else
pVTR->close();
}
}
// If for some reason, we did not have two modelspace viewports,
// lets stop here.
if(pLeftVTR == NULL)
{
if(pRightVTR != NULL)
pRightVTR->close();
return;
}
if(pRightVTR == NULL)
{
if(pLeftVTR != NULL)
pLeftVTR->close();
return;
}
// Ensure that the two viewports are split vertically in half.
// If not, the view parameters when applied from one to another
// may not apply directly using this code.
// If the viewports were resized manually, set them right.
AcGePoint2d ll1 = pLeftVTR->lowerLeftCorner();
AcGePoint2d ur1 = pLeftVTR->upperRightCorner();
AcGePoint2d ll2 = pRightVTR->lowerLeftCorner();
AcGePoint2d ur2 = pRightVTR->upperRightCorner();
if(ll1.isEqualTo(AcGePoint2d(0.0, 0.0)) == false)
{
if(! pLeftVTR->isWriteEnabled())
pLeftVTR->upgradeOpen();
pLeftVTR->setLowerLeftCorner(AcGePoint2d(0.0, 0.0));
}
if(ur1.isEqualTo(AcGePoint2d(0.5, 1.0)) == false)
{
if(! pLeftVTR->isWriteEnabled())
pLeftVTR->upgradeOpen();
pLeftVTR->setUpperRightCorner(AcGePoint2d(0.5, 1.0));
}
if(ll2.isEqualTo(AcGePoint2d(0.5, 0.0)) == false)
{
if(! pRightVTR->isWriteEnabled())
pRightVTR->upgradeOpen();
pRightVTR->setLowerLeftCorner(AcGePoint2d(0.5, 0.0));
}
if(ur2.isEqualTo(AcGePoint2d(1.0, 1.0)) == false)
{
if(! pRightVTR->isWriteEnabled())
pRightVTR->upgradeOpen();
pRightVTR->setUpperRightCorner(AcGePoint2d(1.0, 1.0));
}
// Get the active model space viewport
struct resbuf res;
acedGetVar(L"CVPORT", &res);
short vpnumber = res.resval.rint;
// Identify the model space viewports from/to which settings will be copied
// The active modelspace viewport is the viewport from which settings will be copied
AcDbViewportTableRecord *pFromVTR = NULL;
AcDbViewportTableRecord *pToVTR = NULL;
if(pLeftVTR->number() == vpnumber)
{
pFromVTR = pLeftVTR;
pToVTR = pRightVTR;
}
if(pRightVTR->number() == vpnumber)
{
pFromVTR = pRightVTR;
pToVTR = pLeftVTR;
}
// Sorry, we did not identify the active viewport
// from which settings need to be copied.
if(pFromVTR == NULL || pToVTR == NULL)
return;
// Copy the VTR settings from one modelspace viewport to another
// only if they are different. We will use a tolerance to ensure
// very small differences do not get us in a soup. I meant loop :)
AcGeTol newTol;
newTol.setEqualPoint (0.00001);
newTol.setEqualVector(0.00001);
// ViewDirection
AcGeVector3d fromViewDir = pFromVTR->viewDirection();
AcGeVector3d toViewDir = pToVTR->viewDirection();
if(pFromVTR->viewDirection().isEqualTo(pToVTR->viewDirection(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setViewDirection(pFromVTR->viewDirection());
updateNeeded = Adesk::kTrue;
}
// ViewTwist
if(abs(pFromVTR->viewTwist() - pToVTR->viewTwist()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setViewTwist(pFromVTR->viewTwist());
updateNeeded = Adesk::kTrue;
}
// Target
if(pFromVTR->target().isEqualTo(pToVTR->target(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setTarget(pFromVTR->target());
updateNeeded = Adesk::kTrue;
}
// BackClipEnabled
if(pFromVTR->backClipEnabled() != pToVTR->backClipEnabled())
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setBackClipEnabled(pFromVTR->backClipEnabled());
updateNeeded = Adesk::kTrue;
}
// BackClipDistance
if(abs(pFromVTR->backClipDistance() - pToVTR->backClipDistance()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setBackClipDistance(pFromVTR->backClipDistance());
updateNeeded = Adesk::kTrue;
}
// FrontClipEnabled
if(pFromVTR->frontClipEnabled() != pToVTR->frontClipEnabled())
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setFrontClipEnabled(pFromVTR->frontClipEnabled());
updateNeeded = Adesk::kTrue;
}
// FrontClipDistance
if(abs(pFromVTR->frontClipDistance() - pToVTR->frontClipDistance()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setFrontClipDistance(pFromVTR->frontClipDistance());
updateNeeded = Adesk::kTrue;
}
// Elevation
if(abs(pFromVTR->elevation() - pToVTR->elevation()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setElevation(pFromVTR->elevation());
updateNeeded = Adesk::kTrue;
}
// centerPoint
if(pFromVTR->centerPoint().isEqualTo(pToVTR->centerPoint(), newTol) == false)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setCenterPoint(pFromVTR->centerPoint());
updateNeeded = Adesk::kTrue;
}
// Height
if(abs(pFromVTR->height() - pToVTR->height()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setHeight(pFromVTR->height());
updateNeeded = Adesk::kTrue;
}
// Width
if(abs(pFromVTR->width() - pToVTR->width()) > 0.01)
{
if(! pToVTR->isWriteEnabled())
pToVTR->upgradeOpen();
pToVTR->setWidth(pFromVTR->width());
updateNeeded = Adesk::kTrue;
}
// Done with the VTR
pLeftVTR->close();
pRightVTR->close();
delete pIter;
}
es = pVT->close();
es = acDocManager->unlockDocument(pDoc);
// Update the Vports if we did change any of the VTR parameters
if(updateNeeded)
{
acedVportTableRecords2Vports();
}
}
};
//-----------------------------------------------------------------------------
IMPLEMENT_ARX_ENTRYPOINT(CMyTest1App)
ACED_ARXCOMMAND_ENTRY_AUTO(CMyTest1App, AdskMyTest, SyncVTR, SyncVTR, ACRX_CMD_MODAL, NULL)
https://adndevblog.typepad.com/autocad/2014/07/synchronizing-model-space-viewports.html
我找到了两个解决问题的应用程序,DWGsync(在 autocad 2021 之前免费且兼容)https://apps.autodesk.com/ACD/es/Detail/Index?id=2788892389049910944&appLang=en&os=Win32_64 and Drawing Sync (licensed and compatible autocad 2018) https://apps.autodesk.com/ACD/en/Detail/Index?id=2152736212918385179&appLang=en&os=Win32_64