前言
谈到国产自研CAD软件,有一个绕不开的词——DWG格式,毕竟在相对漫长的CAD软件历史中,大多数的专业图纸几乎都是用AutoCAD绘制的,保存格式为 .dwg格式。 而 .dwg格式又非开源文件格式,在没有AutoDesk公司帮助下,别的软件想要读取或修改dwg格式有着难以想象的难度,即使你有能力破解了dwg格式的读写,也会面临着AutoDesk公司的诉讼,毕竟人家有专利保护,实际上这也是一种变相的垄断,天下苦欧特克AUTOCAD久已!。下面以国产CAD龙头中望软件为例简单介绍ITC组织和Open Design联盟(ODA)。
1、中望官司风波
2020年4月9日,广州中望龙腾软件股份有限公司(下文简称“中望”)向科创板提交招股书,拟募资6.01亿元。在中望官网,自我介绍是“国内唯一同时拥有完全自主知识产权二维中望CAD、高端三维CAD/CAM软件中望3D的国际化软件企业。”但这个被中望一再强调的“完全自主知识产权”,究竟有多少含金量?
2013年,《中国新闻出版报》发布文章《ITC禁售引发国产CAD软件生存危机》,称ITC组织将于禁止对华销售InterlliCAD7以下版本的CAD产品,这对国产CAD软件而言是巨大的挑战,很可能导致许多软件无法再对用户销售。当时为了应对这一政策,中望CAD放弃了基于 ITC 的产品,转向自己开发 ZWCAD+产品,本来以为中望软件研发出了自主内核的CAD平台软件。但好景不长,就在这个宣称不受ITC影响的中望CAD+发布一年多后,竞争对手AutoCAD却找上了门来。
2014年,开发AutoCAD软件的母公司欧特克公司在荷兰、美国两地起诉中望,称“AutoCAD源代码被盗用并被不当使用于ZWCAD+的开发。 在2017年中望挂牌新三板的《转让说明书》中,这一诉讼案的背后故事被公之于众。中望被要求赔偿欧特克总计180万美元,分3年共4次支付,单次分别支付40万、45万、45万、50万美元。此次案件最终以中望软件禁售ZWCAD+系列产品,并向Autodesk(欧特克)赔款而告终。兜兜转转一大圈,中望最终还是回归了ITC的怀抱,并租用至今。
在ITC组织的官网上,无论是在其官网首页,还是组织会员列表中,中望CAD都在其列,并清晰的展示了基于IntelliCAD的最新产品中望CAD2020(ZWCAD2020)。在ITC组织发布的新闻稿中更多次强调“ZWCAD是运行在IntelliCAD引擎之上”,ITC官网还明确展示了中望成为会员获得的技术收益,包括熟悉的CAD用户界面、DWG格式的原生兼容、获得关键CAD引擎的源代码等等。
ODA官网的价格信息显示,中望购买了ODA的会员才获得其技术使用权,且每年要支付年费。最贵的企业会员年费在3万美元以上。以ODA提供的dwg技术为例,dwg是中望最大的竞争对手AutoCAD软件的专有文件格式,其他CAD软件厂商想与之竞争,就必须有dwg文件的读写和转换能力,以对AutoCAD兼容,使用户能够在不同软件平台间切换。查阅ODA官网,中望CAD作为会员代表出现,并提到ODA提供快速、稳定及可靠的.dwg技术来帮助其构建ZWCAD内核。
2、ODA技术联盟和ITC组织
ODA(Open Design Alliance)是一个非盈利技术联盟,由1200个成员公司组成,申请成为付费会员后能够使用其图形技术库。ODA致力于促进开放的、工业标准的CAD数据和遗留的CAD数据的格式交换。 ODA开发用于技术图形应用程序的核心平台Teigha™, Teigha支持dwg、dgn、stl、pdf之间的数据交换。Teigha支持的多个平台:Windows、Mac、Unix、Linux等。 ODA 会员可以用 C++, .NET, 和 ActiveX 接口开发自己的应用程序。ODA的宗旨是开发核心的图形技术库,让软件开发商专注与应用开发。 和ITC一样也是面向会员的。
ITC(IntelliCAD Technology Consortium)是由CAD开发公司组成的一个非盈利组织,拥有版权并负责开发和管理IntelliCAD。ITC会员在缴纳会费后,可享有使用美国IntelliCAD源代码的权利,并发布OEM品牌的CAD软件。IntelliCAD 最初的创建是为了阻止 Autodesk 完全垄断 CAD,让数百万用户能够在 Autodesk 产品之外编辑数十亿个工程数据文件。
IntelliCAD 是一个专业级的商业 2D/3D CAD 软件程序和解决方案开发平台。IntelliCAD 的核心数据库依赖于 ODA 平台来打开、可视化、编辑和保存 .dwg 和 .dgn 文件。IntelliCAD是一个CAD编辑器和开发平台,具有由IntelliCAD 技术联盟(“ITC”)通过共享开发发布的应用程序编程接口API 。IntelliCAD 模拟了AutoCAD的基本界面和功能,但是,它特别能够在各种文件类型(即 dwg.、BIM、TIFF 等)之间自由合并和交换。ITC IntelliCAD 不直接出售给最终用户,而是授权给联盟成员,他们通过支付年费来支持共享开发,类似于合作安排,以换取在全球范围内分发基于 IntelliCAD 的解决方案的许可。
3、Teigha SDK常用API
在CAD(计算机辅助设计)领域,由Open Design Alliance (ODA) 开发的Teigha SDK无疑是一个强大的工具,它为开发者提供了一套全面的工具包,用于处理各种CAD文件。本仓库不仅包含了Teigha SDK 4.2的官方文档,还提供了丰富的使用示例,帮助开发者快速上手并集成该SDK到他们的项目中。https://gitcode.com/open-source-toolkit/a2de8/overview?utm_source=tools_gitcode&index=top&type=card&webUrl
3.1 ODA实体
3.1.1 ODA实体分类
std::wstring GetOdDbEntityClassName(const OdDbObjectId& entId)
{
std::wstring sClassName = L"";
OdDbEntityPtr pOdEnt = entId.safeOpenObject();
if (!pOdEnt.isNull())
{
if (pOdEnt->isKindOf(OdDbPoint::desc()))
{
sClassName = L"点";
}
else if (pOdEnt->isKindOf(OdDbLine::desc()))
{
sClassName = L"线段";
}
else if (pOdEnt->isKindOf(OdDbMline::desc()))
{
sClassName = L"多线";
}
else if (pOdEnt->isKindOf(OdDbPolyline::desc()))
{
sClassName = L"多段线";
}
else if (pOdEnt->isKindOf(OdDb2dPolyline::desc()))
{
sClassName = L"二维多段线";
}
else if (pOdEnt->isKindOf(OdDb3dPolyline::desc()))
{
sClassName = L"三维多段线";
}
else if (pOdEnt->isKindOf(OdDbXline::desc()))
{
sClassName = L"构造线";
}
else if (pOdEnt->isKindOf(OdDbRay::desc()))
{
sClassName = L"射线";
}
else if (pOdEnt->isKindOf(OdDbArc::desc()))
{
sClassName = L"圆弧";
}
else if (pOdEnt->isKindOf(OdDbCircle::desc()))
{
sClassName = L"圆";
}
else if (pOdEnt->isKindOf(OdDbEllipse::desc()))
{
sClassName = L"椭圆";
}
else if (pOdEnt->isKindOf(OdDbHatch::desc()))
{
sClassName = L"填充";
}
else if (pOdEnt->isKindOf(OdDbSpline::desc()))
{
sClassName = L"样条线";
}
else if (pOdEnt->isKindOf(OdDbText::desc()))
{
sClassName = L"文字";
}
else if (pOdEnt->isKindOf(OdDbMText::desc()))
{
sClassName = L"多行文字";
}
else if (pOdEnt->isKindOf(OdDbSolid::desc()))
{
sClassName = L"体";
}
else if (pOdEnt->isKindOf(OdDbRegion::desc()))
{
sClassName = L"面域";
}
else if (pOdEnt->isKindOf(OdDbShape::desc()))
{
sClassName = L"形状";
}
else if (pOdEnt->isKindOf(OdDbSurface::desc()))
{
sClassName = L"曲面";
}
else if (pOdEnt->isKindOf(OdDbBlockReference::desc()))
{
sClassName = L"块参照";
}
else if (pOdEnt->isKindOf(OdDbRadialDimension::desc()))
{
sClassName = L"半径标注";
}
else if (pOdEnt->isKindOf(OdDbDiametricDimension::desc()))
{
sClassName = L"直径标注";
}
else if (pOdEnt->isKindOf(OdDb2LineAngularDimension::desc()))
{
sClassName = L"角度标注";
}
else if (pOdEnt->isKindOf(OdDb3PointAngularDimension::desc()))
{
sClassName = L"3d角度标注";
}
else if (pOdEnt->isKindOf(OdDbAlignedDimension::desc()))
{
sClassName = L"对齐标注";
}
else if (pOdEnt->isKindOf(OdDbArcDimension::desc()))
{
sClassName = L"弧长标注";
}
else if (pOdEnt->isKindOf(OdDbOrdinateDimension::desc()))
{
sClassName = L"坐标标注";
}
else if (pOdEnt->isKindOf(OdDbRadialDimensionLarge::desc()))
{
sClassName = L"折弯标注";
}
else if (pOdEnt->isKindOf(OdDbLeader::desc()))
{
sClassName = L"引线标注";
}
else if (pOdEnt->isKindOf(OdDbMLeader::desc()))
{
sClassName = L"多引线标注";
}
else if (pOdEnt->isKindOf(OdDbRotatedDimension::desc()))
{
sClassName = L"线性标注";
}
else if (pOdEnt->isKindOf(OdDbOle2Frame::desc()))
{
sClassName = L"OLE";
}
else if (pOdEnt->isKindOf(OdDbRasterImage::desc()))
{
sClassName = L"图片";
}
}
return sClassName;
}
3.1.2 绘制直线
void DrawLines()
{
OdGePoint3d startPoint;
OdGePoint3d endPoint;
OdDbLinePtr pLineOd = OdDbLine::createObject();
pLineOd->setStartPoint(startPoint);
pLineOd->setEndPoint(endPoint);
OdDbObjectId idNew = appendOdDbEntity(pLineOd);
}
3.1.3 添加实体到数据库
OdDbHostAppServices* m_pSystemService = new OdDbHostAppServices();
OdDbDatabasePtr m_pDb = m_pSystemService->createDatabase();
OdDbObjectId appendOdDbEntity(OdDbEntityPtr pEnt)
{
OdSmartPtr<OdDbBlockTableRecord> pModelSpace = m_pDb->getModelSpaceId().safeOpenObject(OdDb::kForWrite);
std::wstring sLayerName = pEnt->layer();
if (sLayerName.empty())
{
std::wstring sLayerDefault = OdDbSymUtil::getSymbolName(m_pDb->getCLAYER());
if (sLayerDefault != L"0")
{
pEnt->setLayer(m_pDb->getCLAYER());
}
}
OdDbObjectId id = pModelSpace->appendOdDbEntity(pEnt);
return id;
}
3.1.4 读取直线起点
GraphPoint3d startPoint(const OdDbObjectId& entId)
{
OdGePoint3d ptStart = OdGePoint3d::kOrigin;
OdDbEntityPtr pOdEnt = entId.safeOpenObject();
if (!pOdEnt.isNull())
{
if (pOdEnt->isKindOf(OdDbLine::desc()))
{
OdDbLine* pLine = dynamic_cast<OdDbLine*> (pOdEnt.get());
ptStart = pLine->startPoint();
}
}
return ptStart;
}
3.1.5 修改直线起点
void setStartPoint(const OdDbObjectId& entId,const OdGePoint3d& ptStart)
{
OdDbEntityPtr pOdEnt = entId.safeOpenObject(OdDb::kForWrite);
if (!pOdEnt.isNull())
{
if (pOdEnt->isKindOf(OdDbLine::desc()))
{
OdDbLine* pLine = dynamic_cast<OdDbLine*> (pOdEnt.get());
pLine->setStartPoint(ptStart);
}
}
}
3.1.6 依据块名称获取块Id
OdDbObjectId getBolckIdFromDB(OdDbDatabasePtr pDb, std::wstring sBlockName)
{
OdDbObjectId id = OdDbObjectId(0);
if (!pDb || sBlockName.empty())
{
return id;
}
OdDbBlockTablePtr pBlockTable = pDb->getBlockTableId().safeOpenObject(OdDb::kForRead);
if (!pBlockTable)
{
return id;
}
OdDbObjectId pBlockRecordId = pBlockTable->getAt(OdString(sBlockName.c_str()));
if (pBlockRecordId.isNull())
{
return id;
}
return pBlockRecordId;
}
3.2 ODA图层
3.2.1 获取所有图层名称列表
int getAllLayerNames(OdDbDatabasePtr pDb, std::vector<std::wstring>& layerNames)
{
if (!pDb)
{
return 0;
}
OdDbLayerTablePtr pLayers = pDb->getLayerTableId().safeOpenObject();
if (!pLayers)
{
return 0;
}
for (OdDbSymbolTableIteratorPtr pIter = pLayers->newIterator(); !pIter->done(); pIter->step())
{
OdDbLayerTableRecordPtr pLayer = pIter->getRecord();
if (!pLayer)
{
continue;
}
std::wstring sName = pLayer->getName();
layerNames.emplace_back(sName);
}
return (int)layerNames.size();
}
3.2.2 依据图层名称获取图层ID
OdDbObjectId getLayerOdIdFromDB(OdDbDatabasePtr pDb, std::wstring sLayerName)
{
OdDbObjectId id = OdDbObjectId(0);
if (!pDb || sLayerName.empty())
{
return id;
}
OdDbLayerTablePtr pLayerTable = pDb->getLayerTableId().safeOpenObject(OdDb::kForRead);
if (!pLayerTable)
{
return id;
}
OdDbObjectId pLayerRecordId = pLayerTable->getAt(OdString(sLayerName.c_str()));
if (pLayerRecordId.isNull())
{
return id;
}
return pLayerRecordId;
}
3.3 ODA线型
3.3.1 获取线型列表
std::vector<std::wstring> getLineTypeNames(OdDbDatabasePtr pDb)
{
std::vector<std::wstring> vecLineType;
OdDbLinetypeTablePtr pLineTypeTable = pDb->getLinetypeTableId().safeOpenObject();
if (!pLineTypeTable.isNull())
{
OdDbSymbolTableIteratorPtr pIt = pLineTypeTable->newIterator();
if (!pIt.isNull())
{
for (pIt->start(); !pIt->done(); pIt->step())
{
OdDbObjectId odLineTypeId = pIt->getRecordId();
wstring sName = getLineTypeIdByName(odTextStyleId);
if (!sName.empty())
{
vecLineType.push_back(sName);
}
}
}
}
return vecL
ineType;
}
3.3.2 依据线型名称获取线型ID
OdDbObjectId getLineTypeIdByName(OdDbDatabasePtr pDb, std::wstring sLineTypeName)
{
OdDbObjectId pLineTypeId = OdDbObjectId(0);
if (!pDb || sLineTypeName.empty())
{
return pLineTypeId;
}
OdDbLinetypeTablePtr pLineTypeTable = pDb->getLinetypeTableId().safeOpenObject(OdDb::kForRead);
if (!pLineTypeTable)
{
return pLineTypeId;
}
OdDbObjectId pLineTypeRecordId = pLineTypeTable->getAt(OdString(sLineTypeName.c_str()));
if (pLineTypeRecordId.isNull())
{
return pLineTypeId;
}
return pLineTypeRecordId;
}
3.3.3 依据线型ID获取线型名称
std::wstring getLineTypeIdByName(OdDbDatabasePtr pDb, OdDbObjectId pLineTypeId )
{
std::wstring sLineTypeName;
if (!pDb || pLineTypeId.isNull())
{
return sLineTypeName;
}
OdDbLinetypeTablePtr pLineTypes = pDb->getLinetypeTableId().safeOpenObject();
if (pLineTypes.isNull())
{
return sLineTypeName;
}
OdDbSymbolTableIteratorPtr pIt = pLineTypes->newIterator();
if (pIt.isNull())
{
return sLineTypeName;
}
for (pIt->start(); !pIt->done(); pIt->step())
{
OdDbObjectId odLineTpyeId = pIt->getRecordId();
OdDbLinetypeTableRecordPtr pLineType = odLineTpyeId.safeOpenObject();
sLineTypeName = pLineType->getName();
if (odLineTpyeId == pLineTypeId)
{
return sLineTypeName;
}
}
return sLineTypeName;
}
3.4 ODA文字样式
3.4.1 依据文字样式名称获取文字样式ID
OdDbObjectId getTextStyleIdFromDB(OdDbDatabasePtr pDb, std::wstring sTextStyleName)
{
OdDbObjectId id = OdDbObjectId(0);
if (!pDb || sTextStyleName.empty())
{
return id;
}
OdDbTextStyleTablePtr pTextStyleTable = pDb->getTextStyleTableId().safeOpenObject(OdDb::kForRead);
if (!pTextStyleTable)
{
return id;
}
OdDbObjectId pTextStyleRecordId = pTextStyleTable->getAt(OdString(sTextStyleName.c_str()));
if (pTextStyleRecordId.isNull())
{
return id;
}
return pTextStyleRecordId;
}
3.4.2 依据文字样式ID称获取文字样式名称
std::wstring getTextStyleName(const OdDbObjectId& styleId)
{
if (!styleId.isValid())
return L"";
std::wstring sName;
OdDbTextStyleTableRecordPtr pRec = styleId.safeOpenObject(OdDb::kForRead);
if (pRec)
{
sName = pRec->getName();
}
return
sName;
}
3.4.3 获取文字样式列表
std::vector<std::wstring> getTextStyleNames(OdDbDatabasePtr pDb)
{
std::vector<std::wstring> vecAllCadTextStyle;
OdDbTextStyleTablePtr pTextStyles = pDb->getTextStyleTableId().safeOpenObject();
if (!pTextStyles.isNull())
{
OdDbSymbolTableIteratorPtr pIt = pTextStyles->newIterator();
if (!pIt.isNull())
{
for (pIt->start(); !pIt->done(); pIt->step())
{
OdDbObjectId odTextStyleId = pIt->getRecordId();
wstring sName = getTextStyleName(odTextStyleId);
if (!sName.empty())
{
vecAllCadTextStyle.push_back(sName);
}
}
}
}
return vecAllCadTextStyle;
}
3.5 ODA扩展数据和数据字典
3.5.1 设定扩展数据XData
void setXData(OdDbObjectId& objId)
{
double unit = 1.0;
OdDbRayPtr pEnt(objId.safeOpenObject(OdDb::kForWrite));
if (pEnt)
{
OdResBufPtr pXd = pEnt->xData(OD_T("RAY_END_POINT_LEN"));
if (pXd)
{
pXd->setNext(OdResBuf::newRb(OdResBuf::kDxfXdReal, unit));
pEnt->setXData(pXd);
}
else
{
pXd = OdResBuf::newRb(1001, OD_T("RAY_END_POINT_LEN"));
pXd->setNext(OdResBuf::newRb(OdResBuf::kDxfXdReal, unit));
pEnt->setXData(pXd);
}
}
}
3.5.2 设置数据字典中字段值
bool SaveOdDbDictionary(OdDbDatabasePtr pDb, wstring sDictName, wstring sKey, wstring sValue)
{
if (pDb.isNull())
return false;
wstring sKeyValue = sValue;
if (sDictName.empty() || sKey.empty() || sKeyValue.empty())
return false;
OdDbDictionaryPtr spNameDict = pDb->getNamedObjectsDictionaryId().safeOpenObject();
OdDbObjectId dictId = spNameDict->getAt(OdString(sDictName.c_str()));
if (!dictId.isValid())
{
spNameDict->upgradeOpen();
OdDbDictionaryPtr pNewDict = OdDbDictionary::createObject();
dictId = spNameDict->setAt(OdString(sDictName.c_str()), pNewDict);
if (!dictId.isValid())
return false;
}
OdDbDictionaryPtr spDict = dictId.safeOpenObject(OdDb::kForWrite);
if (spDict == nullptr)
return false;
wstring sKeyName = sKey;
OdDbObjectId xRecId = spDict->getAt(sKeyName.c_str());
if (!xRecId.isValid())
{
OdDbXrecordPtr pXrecord = OdDbXrecord::createObject();
xRecId = spDict->setAt(sKeyName.c_str(), pXrecord);
if (!xRecId.isValid())
return false;
}
OdDbXrecordPtr pXrecord = xRecId.safeOpenObject(OdDb::kForWrite);
if (pXrecord == nullptr)
return false;
OdResBufPtr pRb, temp;
temp = pRb = OdResBuf::newRb(OdResBuf::kDxfXdAsciiString);
temp->setString(sKeyValue.c_str());
pXrecord->setFromRbChain(temp);
return true;
}
3.5.3 获取数据字典中字段值
bool GetOdDbDictionary(OdDbDatabasePtr pDb, wstring sDictName, wstring sKey, wstring& sValue)
{
if (pDb.isNull())
return false;
OdDbDictionaryPtr spNameDict = pDb->getNamedObjectsDictionaryId().safeOpenObject();
OdDbObjectId dictId = spNameDict->getAt(OdString(sDictName.c_str()));
if (!dictId.isValid())
return false;
OdDbDictionaryPtr spDict = dictId.safeOpenObject(OdDb::kForWrite);
if (spDict == nullptr)
return false;
wstring sKeyName = sKey;
OdDbObjectId xRecId = spDict->getAt(sKeyName.c_str());
if (!xRecId.isValid())
return false;
OdDbXrecordPtr pXrecord = xRecId.safeOpenObject(OdDb::kForWrite);
if (pXrecord == nullptr)
return false;
OdResBufPtr pResBuf = pXrecord->rbChain();
wstring sGetString;
while (!pResBuf.isNull())
{
if (OdResBuf::kDxfXdAsciiString == pResBuf->restype())
{
sGetString = pResBuf->getString();
}
pResBuf = pResBuf->next();
}
if (sGetString.empty())
return false;
sValue = sGetString;
return true;
}