普通视图

发现新文章,点击刷新页面。
昨天以前Fl0w3r

中车大同旧图纸转换定制项目总结

作者 Yousazoe
2023年2月2日 22:36

引言

中车大同的旧图纸转换功能的定制开发告一段落,这段时间和周工学到了很多有用的知识,特此记录这个项目学到的编程技巧和项目开发经验。

容器越界问题

在这个项目中我有很多时候对数组越界问题并不敏感,导致在一些情况下程序直接崩溃了,下面举一些具体的例子:

bool parseDTTitleBar(const CString & filePath)
{
const auto titleBar = parseFile(filePath, titleBarSection[0]);
const auto splitTitleBar = splitProfileString(titleBar);
for (auto& item : splitTitleBar)
{
auto vec = item.second;
+if (!vec.empty() && vec.size() == 10)
titleBarDefItems_.emplace_back(item.first, vec[0], vec[1], _ttof(vec[5]), _ttof(vec[6]), _ttof(vec[7]), vec[4], _ttof(vec[2]), _ttof(vec[3]), vec[8], vec[9]);
}

if (titleBarDefItems_.empty())
return false;

return true;
}

本例中我去实例化一个对象,但使用的方式是直接选取 vec 元素没有增加数量判断。倘若 vec 只有 6 个元素但代码中却取到了 vec[9] 就会导致崩溃。

前向声明

前向声明是我编程时忽略掉的一个细节,在之前学校里写的代码只要编译能过去就不考虑这些问题了,但工作中需要注意效率。

#pragma once

#include "jsoncpp/value.h"
#include "EditableListCtrl.h"
-#include "AbstractDetailsCreator.h"

// DlgCvtMain 对话框

#include <vector>

class DataRow;
class AbstractPaperSelector;
class PaperCreator;
class PaperSizeDefinition;
+class AbstractDetailsCreator;

#include 所做的就是将整个代码复制过来,而这里我们并不关心 AbstractDetailsCreator 具体如何是什么,只需要知道有这样一个类型存在即可,这时就可以用前置声明而非引入整个头文件。

从代码编写的优雅程度来讲这样也会让阅读代码更加容易,不会因为引入过多无关头文件而一头雾水。前置声明最大的好处在于避免编译膨胀,一个优秀的 CPP 代码应该只包含它的最小代码集合。

实体的 XData 读取

自己经常忘记如何读取实体的 XData,特此记录:

AcDbObjectPointer<AcDbText> text(textId);
if (text.openStatus() != eOk) continue;

const auto xData = text->xDataPtr(TitleTextAppName);
if (xData.isNull() || xData->next().isNull() || xData->next()->restype() != OdResBuf::kDxfXdAsciiString)
continue;

CString itemName = xData->next()->getString();
...

获取链表后根据所需类型选择相应 get 函数即可。

以组为单位的图框定位点平移

大同希望把我们产品的图框插入点由内框左下点改为外框左下点,需要以组为单位做一下平移操作。思路上还是比较清晰的:

  1. 获取当前图纸中图框所在的组
  2. 以国标为例需要根据装订线有无确定平移所需的偏移量
  3. 遍历组进行平移操作
AcDbDictionaryPointer dict(acdbCurDwg()->groupDictionaryId(), kForRead);
if (dict.openStatus() != eOk) return;

AcDbObjectPointer<AcDbGroup> paperGroup;
if (dict->getAt(_T("PAPERGROUP"), (AcDbGroup*&)paperGroup, kForRead) != Acad::eOk) return;

AcDbObjectPointer<AcDbGroup> group(paperGroup->objectId(), kForRead);
if (group.openStatus() != eOk) return;

AcGeMatrix3d mat;
double xOffset = 0.0, yOffset = 0.0;
if (g_paper.bZhuangding)
{
xOffset = g_paper.a;
yOffset = g_paper.c;
}
else
xOffset = yOffset = g_paper.e;

AcGeVector3d transVec(xOffset, yOffset, 0);
mat.setToTranslation(transVec);

unique_ptr<AcDbGroupIterator> groupIter(group->newIterator());
if (groupIter == nullptr) return;

for (; !groupIter->done(); groupIter->next())
{
// 从组中获取块引用
AcDbObjectPointer<AcDbBlockReference> blkRef(groupIter->objectId(), kForWrite);
if (blkRef.openStatus() != eOk) continue;

blkRef->transformBy(mat);
}

附加栏缩放

附加栏缩放方式由变换矩阵改为修改实体的缩放因子:

AcDbObjectId idEnt;
if (acdbGetObjectId(idEnt, entIns) == Acad::eOk)
{
AcDbObjectPointer<AcDbBlockReference> pEntity(idEnt, AcDb::kForWrite);
if (pEntity.openStatus() == Acad::eOk)
{
- AcGeMatrix3d matrix;
- matrix.setToScaling(scale);
- pEntity->transformBy(matrix);

+ pEntity->setScaleFactors({ scale, scale, scale });
}
}

重写读取 .ini 文件函数

业务场景

业务场景需要读取下面的 .def 配置文件:

[Info]
bindingEditable=0
bindingDefault=1
bindingAreaWidth=20
bindingEtcAreaWidth=5
notBindingWidth=0

splitEditable=1
splitDefault=1
splitTextHeight=3.5

middleEditable=1
middleDefault=1

attachBarEditable=0
attachBarDefault=0

mainTitleBarHeight=63
bomHeaderHeight=9.993
bomTextBlockHeight=7
bomTextBlockExtHeight=10

[DTTitleBar]
main= 主标题栏; DTTitleBar; 185; 63; IRB; 0; 0; 0; bkt-1en_main.dwg;
lb= 副标题栏签字; DTTitleBar; 20; 287; ILB; 0; 0; 0; bkt-1en_lb.dwg; A3:0.75,A4:0.5,A4+:0.75
lt= 副标题栏图样代码; DTTitleBar; 137; 21; ILT; 0; 0; 0; bkt-1_lt.dwg;

[DTTitleBarText]
IRB001 = 图样代码; 5; IRB; -60; 47.5; 0; MC; 120; STR; 0; 11; Standard; main; ;
IRB002 = 中文名称; 5; IRB; -85; 33.75; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB003 = 中文材料; 5; IRB; -85; 11.25; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB004_ZhiLiang = 质量; 4; IRB; -26.5; 27.5; 0; MC; 17; STR; 0; 11; Standard; main; ;
IRB005_BiLi = 比例; 4; IRB; -9; 27.5; 0; MC; 18; STR; 0; 11; Standard; main; ;
IRB006 = 共张; 3.5; IRB; -15; 17.5; 0; MC; 30; STR; 0; 11; Standard; main; ;
IRB007 = 第张; 3.5; IRB; -40; 17.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB008_TuFu = 图幅; 4; IRB; -3.5; -2.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB009 = 俄文名称; 5; IRB; -85; 21.25; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB010 = 俄文材料; 5; IRB; -85; 3.75; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB011 = 订货号; 4; IRB; -60; 59; 0; MC; 120; STR; 0; 11; Standard; main; ;
IRB012 = 识别符号1; 4; IRB; -47.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB013 = 识别符号2; 4; IRB; -42.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB014 = 识别符号3; 4; IRB; -37.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB015 = 复印人员; 3.5; IRB; -70; -2.5; 0; ML; 30; STR; 0; 11; Standard; main; ;

ILB001 = (左)国家标准登记号; 4; ILB; 8.5; 12.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB002 = (左)签名和日期1; 4; ILB; 8.5; 42.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;
ILB003 = (左)替代正本号; 4; ILB; 8.5; 72.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB004 = (左)副本登记号; 4; ILB; 8.5; 97.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB005 = (左)签名和日期2; 4; ILB; 8.5; 127.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;
ILB006 = (左)替代文件代号; 4; ILB; 8.5; 197; 90; MC; 60; STR; 0; 11; Standard; lb; ;
ILB007 = (左)相应文件代号; 4; ILB; 8.5; 257; 90; MC; 60; STR; 0; 11; Standard; lb; ;

ILT001 = (上)专利编号; 3; ILT; 35; -17.5; 0; MC; 70; STR; 0; 11; Standard; lt; ;
ILT002 = (上)订货号标记; 3; ILT; 77; -7; 0; MC; 14; STR; 0; 11; Standard; lt; ;
ILT003 = (上)相应文件决议编号和批准年份; 3; ILT; 110.5; -3.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;
ILT004 = (上)本文件决议编号和批准年份; 3; ILT; 110.5; -10.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;

IRB101 = 更改区域1____; 3; IRB; -195; 37.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB102 = 变更序号1; 3; IRB; -181.5; 37.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB103 = 变更页码1; 3; IRB; -173; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
IRB104 = 通知单号1; 3; IRB; -156.5; 37.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB105 = 签名1; 3; IRB; -137.5; 37.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
IRB106 = 日期1; 3; IRB; -125; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

IRB107 = 更改区域2____; 3; IRB; -195; 42.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB108 = 变更序号2; 3; IRB; -181.5; 42.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB109 = 变更页码2; 3; IRB; -173; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
IRB110 = 通知单号2; 3; IRB; -156.5; 42.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB111 = 签名2; 3; IRB; -137.5; 42.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
IRB112 = 日期2; 3; IRB; -125; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

IRB113 = 更改区域3____; 3; IRB; -195; 47.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB114 = 变更序号3; 3; IRB; -181.5; 47.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB115 = 变更页码3; 3; IRB; -173; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
IRB116 = 通知单号3; 3; IRB; -156.5; 47.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB117 = 签名3; 3; IRB; -137.5; 47.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
IRB118 = 日期3; 3; IRB; -125; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

IRB119 = 更改区域4____; 3; IRB; -195; 52.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB120 = 变更序号4; 3; IRB; -181.5; 52.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB121 = 变更页码4; 3; IRB; -173; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
IRB122 = 通知单号4; 3; IRB; -156.5; 52.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB123 = 签名4; 3; IRB; -137.5; 52.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
IRB124 = 日期4; 3; IRB; -125; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

IRB125=设计-文件数量; 3; IRB; -156.5; 27.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB126=审核-文件数量; 3; IRB; -156.5; 22.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB127=主任设计-文件数量; 3; IRB; -156.5; 17.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB128=工艺-文件数量; 3; IRB; -156.5; 12.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB129=标准化-文件数量; 3; IRB; -156.5; 7.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
IRB130=批准-文件数量; 3; IRB; -156.5; 2.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;

[DTTitleBarTextReference]
tydmfz = 图样代码; 3; ILT; 35; -7; 180; MC; 70; STR; 0; 11; Standard; lt; ;IRB001


[DTBOM]
bomHeader= 主明细栏; DTBOMHeader; 185; 10; IRB; 0; 0; 0; bkt-1en_bh.dwg;
bomTextBlock= 明细文字框; DTBOMTextBlock; 185; 7; IRB; 0; 0; 0; btb_w185h7.dwg;
bomTextBlockExt= 明细文字框扩展; DTBOMTextBlock; 185; 10; IRB; 0; 0; 0; btb_w185h10.dwg;

[DTBOMText]
BOM001 = 序号; 3.5; IRB; -178.5; 5; 0; MC; 13; STR; 0; 11; Standard; bomHeader; ;
BOM002 = 代码; 3.5; IRB; -171; 5; 0; ML; 25; STR; 0; 11; Standard; bomHeader; ;
BOM003 = 代号; 3.5; IRB; -146; 5; 0; ML; 30; STR; 0; 11; Standard; bomHeader; ;
BOM004 = 名称; 3.5; IRB; -116; 0; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;
BOM005 = 数量; 3.5; IRB; -81; 5; 0; ML; 8; STR; 0; 11; Standard; bomHeader; ;
BOM006 = 材料; 3.5; IRB; -73; 5; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;
BOM007 = 单件; 3.5; IRB; -38; 5; 0; ML; 12; STR; 0; 11; Standard; bomHeader; ;
BOM008 = 总计; 3.5; IRB; -26; 5; 0; ML; 12; STR; 0; 11; Standard; bomHeader; ;
BOM009 = 附注; 3.5; IRB; -14; 5; 0; ML; 15; STR; 0; 11; Standard; bomHeader; ;

EXT001 = 外文名称; 3.5; IRB; -116; 100; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;
EXT002 = 外文材料; 3.5; IRB; -73; 100; 0; ML; 35; STR; 0; 11; Standard; bomHeader; ;

[DTAttachBar]

[DTAttachBarText]

.ini 文件结构

我之前遇到的大部分配置文件的类型都是 .xml 或者 .josn(现在网络端也是这两者使用比较多,属于通用的配置文件类型了),面对 .ini 文件还是比较陌生。 这里引用简书一位博主的 ini文件格式和读取

ini 就是英文 “initialization” 的头三个字母的缩写,当然 INI file 的后缀名也不一定是 .ini,也可以是 .cfg.conf 或者是 .txt

ini 文件的格式很简单,最基本的三个要素是:parameterssectionscomments

parameters

ini 所包含的最基本的”元素”就是 parameter,每一个 parameter 都有一个 name 和一个 value,如下所示:

name = value
sections

所有的 parameters 都是以 sections 为单位结合在一起的。所有的 section 名称都是独占一行,并且 sections 名字都被方括号包围着([ section's name ])。

section 声明后的所有 parameters 都是属于该 section。对于一个 section 没有明显的结束标志符,一个 section 的开始就是上一个 section 的结束,或者是 end of the file。 section 如下所示:

[section]
comments

在 ini 文件中注释语句是以分号 ; 开始的。所有的所有的注释语句不管多长都是独占一行直到结束的。在分号和行结束符之间的所有内容都是被忽略的。

解决方案

项目的工具类中有之前写好的读取 .ini 配置文件函数:

......
// 从指定配置文件中读取指定段,指定属性的内容
CString IM_PUBLIC_FUNCTION_ IM_GetConfigFileValue ( CString szFileName , CString szSegName , CString szKeyName , CString szDefault = _T("") , int nMaxLength = 512 ) ;
// 向指定配置文件中写入指定段,指定属性的内容
BOOL IM_PUBLIC_FUNCTION_ IM_SetConfigFileValue ( CString szFileName , CString szSegName , CString szKeyName , CString szValue ) ;

#define WritePrivateProfileInt IM_WritePrivateProfileInt
BOOL IM_PUBLIC_FUNCTION_ IM_WritePrivateProfileInt ( LPCTSTR lpAppName , LPCTSTR lpKeyName , int nValue , LPCTSTR lpFileName ) ;

CString IM_PUBLIC_FUNCTION_ IM_GetPrivateProfileString ( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPCTSTR lpFileName , DWORD lMaxSize = 256 ) ;

void IM_PUBLIC_FUNCTION_ IM_GetPrivateProfileSectionMap( CString szAppName, CString szFilePath, map<CString,CString>& mapValue ) ;
// 获取配置文件中所有的段名
void IM_PUBLIC_FUNCTION_ IM_GetPrivateProfileAppNames(const CString &szFilePath, CStringArray &szAppNames);
CString IM_PUBLIC_FUNCTION_ IM_GetSystemSettingProfile () ;
......

这些函数的实现则是依赖于底层 Windows 提供的 API 接口

而在具体实践中,由于业务场景中 .ini 文件的 parametersvalue 值过长,导致读取的时候出现遗漏的情况(下面以读取 [DTTitleBarText] 为例):

std::map<CString, CString> resultMap;
IM_GetPrivateProfileSectionMap(L"DTTitleBarText", filePath, resultMap);

得到的 resultMap 则是缺失了一部分:

[DTTitleBarText]
IRB001 = 图样代码; 5; IRB; -60; 47.5; 0; MC; 120; STR; 0; 11; Standard; main; ;
IRB002 = 中文名称; 5; IRB; -85; 33.75; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB003 = 中文材料; 5; IRB; -85; 11.25; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB004_ZhiLiang = 质量; 4; IRB; -26.5; 27.5; 0; MC; 17; STR; 0; 11; Standard; main; ;
IRB005_BiLi = 比例; 4; IRB; -9; 27.5; 0; MC; 18; STR; 0; 11; Standard; main; ;
IRB006 = 共张; 3.5; IRB; -15; 17.5; 0; MC; 30; STR; 0; 11; Standard; main; ;
IRB007 = 第张; 3.5; IRB; -40; 17.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
IRB008_TuFu = 图幅; 4; IRB; -3.5; -2.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
IRB009 = 俄文名称; 5; IRB; -85; 21.25; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB010 = 俄文材料; 5; IRB; -85; 3.75; 0; MC; 70; STR; 0; 11; Standard; main; ;
IRB011 = 订货号; 4; IRB; -60; 59; 0; MC; 120; STR; 0; 11; Standard; main; ;
IRB012 = 识别符号1; 4; IRB; -47.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB013 = 识别符号2; 4; IRB; -42.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB014 = 识别符号3; 4; IRB; -37.5; 27.5; 0; MC; 5; STR; 0; 11; Standard; main; ;
IRB015 = 复印人员; 3.5; IRB; -70; -2.5; 0; ML; 30; STR; 0; 11; Standard; main; ;

ILB001 = (左)国家标准登记号; 4; ILB; 8.5; 12.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB002 = (左)签名和日期1; 4; ILB; 8.5; 42.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;
ILB003 = (左)替代正本号; 4; ILB; 8.5; 72.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB004 = (左)副本登记号; 4; ILB; 8.5; 97.5; 90; MC; 25; STR; 0; 11; Standard; lb; ;
ILB005 = (左)签名和日期2; 4; ILB; 8.5; 127.5; 90; MC; 35; STR; 0; 11; Standard; lb; ;
ILB006 = (左)替代文件代号; 4; ILB; 8.5; 197; 90; MC; 60; STR; 0; 11; Standard; lb; ;
ILB007 = (左)相应文件代号; 4; ILB; 8.5; 257; 90; MC; 60; STR; 0; 11; Standard; lb; ;

ILT001 = (上)专利编号; 3; ILT; 35; -17.5; 0; MC; 70; STR; 0; 11; Standard; lt; ;
-ILT002 = (上)订货号标记; 3; ILT; 77; -7; 0; MC; 14; STR; 0; 11; Standard; lt; ;
-ILT003 = (上)相应文件决议编号和批准年份; 3; ILT; 110.5; -3.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;
-ILT004 = (上)本文件决议编号和批准年份; 3; ILT; 110.5; -10.5; 0; MC; 53; STR; 0; 11; Standard; lt; ;

-IRB101 = 更改区域1____; 3; IRB; -195; 37.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
-IRB102 = 变更序号1; 3; IRB; -181.5; 37.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
-IRB103 = 变更页码1; 3; IRB; -173; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
-IRB104 = 通知单号1; 3; IRB; -156.5; 37.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB105 = 签名1; 3; IRB; -137.5; 37.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
-IRB106 = 日期1; 3; IRB; -125; 37.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

-IRB107 = 更改区域2____; 3; IRB; -195; 42.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
-IRB108 = 变更序号2; 3; IRB; -181.5; 42.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
-IRB109 = 变更页码2; 3; IRB; -173; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
-IRB110 = 通知单号2; 3; IRB; -156.5; 42.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB111 = 签名2; 3; IRB; -137.5; 42.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
-IRB112 = 日期2; 3; IRB; -125; 42.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

-IRB113 = 更改区域3____; 3; IRB; -195; 47.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
-IRB114 = 变更序号3; 3; IRB; -181.5; 47.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
-IRB115 = 变更页码3; 3; IRB; -173; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
-IRB116 = 通知单号3; 3; IRB; -156.5; 47.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB117 = 签名3; 3; IRB; -137.5; 47.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
-IRB118 = 日期3; 3; IRB; -125; 47.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

-IRB119 = 更改区域4____; 3; IRB; -195; 52.5; 0; MC; 20; STR; 0; 11; Standard; main; ;
-IRB120 = 变更序号4; 3; IRB; -181.5; 52.5; 0; MC; 7; STR; 0; 11; Standard; main; ;
-IRB121 = 变更页码4; 3; IRB; -173; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;
-IRB122 = 通知单号4; 3; IRB; -156.5; 52.5; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB123 = 签名4; 3; IRB; -137.5; 52.5; 0; MC; 15; STR; 0; 11; Standard; main; ;
-IRB124 = 日期4; 3; IRB; -125; 52.5; 0; MC; 10; STR; 0; 11; Standard; main; ;

-IRB125=设计-文件数量; 3; IRB; -156.5; 27.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB126=审核-文件数量; 3; IRB; -156.5; 22.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB127=主任设计-文件数量; 3; IRB; -156.5; 17.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB128=工艺-文件数量; 3; IRB; -156.5; 12.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB129=标准化-文件数量; 3; IRB; -156.5; 7.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;
-IRB130=批准-文件数量; 3; IRB; -156.5; 2.5 ; 0; MC; 23; STR; 0; 11; Standard; main; ;

直觉告诉我可能是缓冲区大小的问题,所以还是查一下微软官方文档看一下实现比较好,可能需要自己去重新实现一下读取函数。

GetPrivateProfileSection function

DWORD GetPrivateProfileSection(
[in] LPCTSTR lpAppName,
[out] LPTSTR lpReturnedString,
[in] DWORD nSize,
[in] LPCTSTR lpFileName
);
  • [in] lpAppName
    The name of the section in the initialization file.

  • [out] lpReturnedString
    A pointer to a buffer that receives the key name and value pairs associated with the named section. The buffer is filled with one or more null-terminated strings; the last string is followed by a second null character.

  • [in] nSize
    The size of the buffer pointed to by the lpReturnedString parameter, in characters.
    The maximum profile section size is 32,767 characters.

  • [in] lpFileName
    The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.

这里比较关键的地方就是这个 nSize,可以看到它是通过一个缓冲区大小的参数设定读取的缓冲区大小,最大可以设置为 32,767 字节。经过周工排查 IM_GetPrivateProfileSectionMap 的缓冲区为 2k 左右,所以需要我们重新编写函数读取。

那么首先我们先重新设定最大的缓冲区大小(也就是刚才的 32,767):

TCHAR buffer[32767] = { 0 };
auto profileSize = GetPrivateProfileSection(appName, buffer, std::size(buffer), filePath);

之后则需要用一个我之前几乎没有用过的 string_view。C++17 中我们可以使用 std::string_view 来获取一个字符串的视图,字符串视图并不真正的创建或者拷贝字符串,而只是拥有一个字符串的查看功能。std::string_viewstd::string 的性能要高很多,因为每个 std::string 都独自拥有一份字符串的拷贝,而 std::string_view 只是记录了自己对应的字符串的指针和偏移位置,当我们在只是查看字符串的函数中可以直接使用 std::string_view 来代替。

读取出来的 buffer 字符串则是以下面形式排列:

name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ... nameN = valueN \0\0

这里我的算法是用左右双指针寻找 =\0,以此类推直至读取到 profileSize

size_t left = 0;
size_t right = sv.find(L'=', left);
if (right == std::wstring_view::npos)
right = profileSize;

std::wstring_view key = sv.substr(left, right - left);

左指针归零,右指针先找到 =。此时观察可以发现左右指针已经可以把 name1 读取出来了:

name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ...
^ ^
l r

接着左指针移到右指针(也就是 = 所在位置)后面一位,右指针找到 \0。此时观察可以发现左右指针又可以把 value1 读取出来了:

name1 = value1 \0 name2 = value2 \0 name3 = value3 \0 ...
^ ^
l r

依此类推,最终完整实现如下:

std::map<CString, CString> Utility::readPrivateProfile(CString appName, CString filePath)
{
TCHAR buffer[32767] = { 0 };
auto profileSize = GetPrivateProfileSection(appName, buffer, std::size(buffer), filePath);
std::wstring_view sv{ buffer, profileSize };

size_t left = 0;
std::map<CString, CString> ret;
while (left < profileSize)
{
size_t right = sv.find(L'=', left);
if (right == std::wstring_view::npos)
right = profileSize;

std::wstring_view key = sv.substr(left, right - left);

left = right + 1;
right = sv.find(L'\0', left);
if (right == std::wstring_view::npos)
right = profileSize;

std::wstring_view value = sv.substr(left, right - left);
left = right + 1;

if (!key.empty())
ret.emplace(CString(key.data(), key.length()), CString(value.data(), value.length()));
}

return ret;
}

智能指针托管导致实体打开失败

在移植的过程中发现序号实体一直没办法添加进去,返回的 eWasOpenForWrite 找了好久,也尝试使用升降读写权限也失败了。后经周工点拨才发现问题所在:

-shared_ptr<XuHaoEntity> pMechXH(new XuHaoEntity(m_MechXHDInfo));
+XuHaoEntity* pMechXH = new XuHaoEntity(m_MechXHDInfo);
...

AcDbBlockTableRecordPointer pModelSpace(IM_GetModalSpaceId(), AcDb::kForWrite);
if (pModelSpace.openStatus() != eOk) return false;

AcDbObjectId entId = AcDbObjectId::kNull;
-if (pModelSpace->appendAcDbEntity(entId, pMechXH.get()) != Acad::eOk)
+if (pModelSpace->appendAcDbEntity(entId, pMechXH) != Acad::eOk)
return false;
pMechXH->close();

由于智能指针的广泛使用,所以我对内存管理这一块并不够敏感。张帆的那本书曾经提到 ObjectARX 是如何管理内存的:

在操作图形数据库的各种对象时,必须遵守 AutoCAD 的打开和关闭对象的协议。该协议确保当对象被访问时在物理内存中,而未被访问时可以被分页存储在磁盘中。创建和打开数据库的对象之后,必须在不用的时候关闭它。

给初学 ObiectARX 的人两个建议。

  1. 不要忘记各种数据库对象的关闭:在打开或创建数据库对象之后,必须尽可能早地关闭它。在初学者所犯的错误中,未及时关闭对象的错误至少占一半!
  2. 不要使用 delete pLine 的语句:对 C++ 比较熟悉的读者,习惯于配对使用 newdelete 运算符,这在 C++ 编程中是一个良好的编程习惯。但是在ObjectARX 的编程中,当编程者使用 appendAcDbEntity 函数将对象添加到图形数据库之后,就需要由图形数据库来操作该对象。

这里的 shared_ptr<TH_XuHaoEntity> 就是问题关键。当我使用智能指针的时候将 XuHaoEntity 同时托管给 AutoCAD 和系统,这就导致在该段代码的作用域结束时会自动销毁该序号实体,而此时它已经被添加到数据库对象当中!内存泄漏的问题也就此诞生了,倘若此时用 Debug 工具测试会直接崩溃,因为该实体根本无法访问(毕竟已经在内存中被删除了)。

这个问题正好对应了建议二:不要使用 delete pLine 的语句。

文字样式表保持同步

天喻的旧图纸中转换时明细表部分需使用文字样式 Standard 的字体、大字体和宽度因子,但创建明细表时字体使用的是 HC_TEXTSTYLE。虽然新建的图纸已经将两者调整为一致,但对于旧图纸而言仍不同步,需要将 Standard 的各项设置到 HC_TEXTSTYLE

AcDbTextStyleTablePointer pTextStyleTable(acdbHostApplicationServices()->workingDatabase(), AcDb::kForRead);
if (pTextStyleTable.openStatus() != Acad::eOk) return;

AcDbObjectId standardTextStyleId, hcTextStyleId;
if (pTextStyleTable->getAt(L"STANDARD", standardTextStyleId) != Acad::eOk || pTextStyleTable->getAt(L"HC_TEXTSTYLE", hcTextStyleId) != Acad::eOk)
return;
pTextStyleTable->close();

AcDbTextStyleTableRecordPointer pStandardTextStyleTableRecord(standardTextStyleId, AcDb::kForRead), pHCTextStyleTableRecord(hcTextStyleId, AcDb::kForWrite);
if (pStandardTextStyleTableRecord.openStatus() != Acad::eOk || pHCTextStyleTableRecord.openStatus() != Acad::eOk)
return;

GCHAR* fileName, *bigFontName;
pStandardTextStyleTableRecord->fileName(fileName);
pStandardTextStyleTableRecord->bigFontFileName(bigFontName);

pHCTextStyleTableRecord->setFileName(fileName);
pHCTextStyleTableRecord->setBigFontFileName(bigFontName);
pHCTextStyleTableRecord->setXScale(pStandardTextStyleTableRecord->xScale());
pHCTextStyleTableRecord->close();
pStandardTextStyleTableRecord->close();

用的是笨办法挨个设置,暂时没找到类似整个拷贝的方式去处理文字样式表。

特殊字插入

大同方提供的 .shx 字体没办法正常显示 字,并且强烈要求增加该字的插入功能。

时间有限,讨论了一下决定先将 字做成 liao.dwg 文件,然后通过 LISP 代码交互并插入:

(defun liao()
(initget 4)
(if (= (setq height (getreal "\n请指定文字高度<2.5>: ")) nil)
(setq height 2.5)
)

(setq echo (getvar "cmdecho"))
(setvar "cmdecho" 0)
(command "insert" "瞭" "S" height "\\" 0)
(setvar "cmdecho" echo)
(princ)
)

代码交付中的文件批量删除操作

项目进度后期需要交付图纸转换的源码,但不会将我们所有项目的源码交付给大同方,所以需要将其余无关项目以头文件和库文件的形式交付。

先前我做交付时会一个一个去编译每个依赖项目,看缺少哪个头文件再加进去,效率很低。后面周工教了如何使用 find 命令去操作文件:

find <file_path> -type f ! -name "*.h" -delete

该命令会将所有非 .h 头文件删除,非常方便。

Bézier Curves

作者 Yousazoe
2022年9月17日 16:41

引言

GAMES101现代图形学入门是由闫令琪老师教授。本次作业我们会通过 de Casteljau 算法来绘制由 4 个控制点表示的 Bézier 曲线。

总览

Bézier 曲线是一种用于计算机图形学的参数曲线。在本次作业中,你需要实现 de Casteljau 算法来绘制由 4 个控制点表示的 Bézier 曲线 (当你正确实现该算法时,你可以支持绘制由更多点来控制的 Bézier 曲线)。

你需要修改的函数在提供的 main.cpp 文件中。

  • bezier: 该函数实现绘制 Bézier 曲线的功能。它使用一个控制点序列和一个 OpenCV::Mat 对象作为输入,没有返回值。它会使 t 在 0 到 1 的范围内进行迭代,并在每次迭代中使 t 增加一个微小值。对于每个需要计算的 t,将调用另一个函数 recursive_bezier,然后该函数将返回在 Bézier 曲线上 t 处的点。最后,将返回的点绘制在 OpenCV::Mat 对象上。
  • recursive_bezier: 该函数使用一个控制点序列和一个浮点数 t 作为输入, 实现 de Casteljau 算法来返回 Bézier 曲线上对应点的坐标。

算法

De Casteljau 算法说明如下:

  1. 考虑一个 p0, p1, … pn 为控制点序列的 Bézier 曲线。首先,将相邻的点连接起来以形成线段。
  2. 用 t : (1 − t) 的比例细分每个线段,并找到该分割点。
  3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。
  4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤 1。

使用 [0,1] 中的多个不同的 t 来执行上述算法,你就能得到相应的 Bézier 曲线。

开始编写

在本次作业中,你会在一个新的代码框架上编写,它比以前的代码框架小很多。和之前作业相似的是,你可以选择在自己电脑的系统或者虚拟机上完成作业。 请下载项目的框架代码,并使用以下命令像以前一样构建项目:

mkdir build
cd build
cmake ..
make

之后,你可以通过使用以下命令运行给定代码 ./BezierCurve。运行时,程序将打开一个黑色窗口。现在,你可以点击屏幕选择点来控制 Bézier 曲线。程 序将等待你在窗口中选择 4 个控制点,然后它将根据你选择的控制点来自动绘制 Bézier 曲线。代码框架中提供的实现通过使用多项式方程来计算 Bézier 曲线并绘制为红色。两张控制点对应的 Bézier 曲线如下所示:

在确保代码框架一切正常后,就可以开始完成你自己的实现了。注释掉 main 函数中 while 循环内调用 naive_bezier 函数的行,并取消对 bezier 函数的注释。要求你的实现将 Bézier 曲线绘制为绿色

如果要确保实现正确,请同时调用 naive_bezierbezier 函数,如果实现正确,则两者均应写入大致相同的像素,因此该曲线将表现为黄色。如果是这样,你可以确保实现正确。

你也可以尝试修改代码并使用不同数量的控制点,来查看不同的 Bézier 曲线。

评分与提交

评分:

  • [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。
  • [20 分] De Casteljau 算法:
    对于给定的控制点,你的代码能够产生正确的 Bézier 曲线。
  • [5 分] 奖励分数:
    实现对 Bézier 曲线的反走样。(对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。)
  • [-2 分] 惩罚分数:
    未删除 /build, /.vscode 和 assignment4.pdf。
    未按格式建立 /images,缺少结果图片。
    未提交或未按要求完成 README.md。
    代码相关文件和 README 文件不在你提交的文件夹下的第一层。

提交:

  • 当你完成作业后,请清理你的项目,记得在你的文件夹中包含 CMakeLists.txt 和所有的程序文件 (无论是否修改);
  • 同时,请新建一个 /images 目录,将所有实验结果图片保存在该目录下;
  • 再添加一个 README.md 文件写清楚自己完成了上述得分点中的哪几点 (如果完成了,也请同时在 images 目录下提交一份结果图片并注明),并简要描述你在各个函数中实现的功能;
  • 最后,将上述内容打包,并用“姓名 Homework4.zip”的命名方式提交到 SmartChair 平台。
    平台链接:http://www.smartchair.org/GAMES101-Spring2021/

实现

代码框架

#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>

std::vector<cv::Point2f> control_points;

void mouse_handler(int event, int x, int y, int flags, void *userdata)
{
if (event == cv::EVENT_LBUTTONDOWN && control_points.size() < 4)
{
std::cout << "Left button of the mouse is clicked - position (" << x << ", "
<< y << ")" << '\n';
control_points.emplace_back(x, y);
}
}

void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window)
{
auto &p_0 = points[0];
auto &p_1 = points[1];
auto &p_2 = points[2];
auto &p_3 = points[3];

for (double t = 0.0; t <= 1.0; t += 0.001)
{
auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;

window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
}
}

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t)
{
// TODO: Implement de Casteljau's algorithm
return cv::Point2f();

}

void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window)
{
// TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
// recursive Bezier algorithm.

}

int main()
{
cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);

cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

int key = -1;
while (key != 27)
{
for (auto &point : control_points)
{
cv::circle(window, point, 3, {255, 255, 255}, 3);
}

if (control_points.size() == 4)
{
naive_bezier(control_points, window);
// bezier(control_points, window);

cv::imshow("Bezier Curve", window);
cv::imwrite("my_bezier_curve.png", window);
key = cv::waitKey(0);

return 0;
}

cv::imshow("Bezier Curve", window);
key = cv::waitKey(20);
}

return 0;
}

De Casteljau算法

$$
b^2_0(t) = (1 - t)^2b_0 + 2t(1 - t)b_1 + t^2b_2
$$

bezier() 函数则调用 recursive_bezier() 算法并将线段颜色设置为绿:

void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
// TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
// recursive Bezier algorithm.
for (float t = 0; t <= 1; t += 0.001f) {
auto point = recursive_bezier(control_points, t);
window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
}
}
朴素算法

由于给定的框架代码有四个控制点,所以我们可以向课程中那样依次推演:

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
// TODO: Implement de Casteljau's algorithm

auto p_0 = control_points[0];
auto p_1 = control_points[1];
auto p_2 = control_points[2];
auto p_3 = control_points[3];

auto p_01 = (1 - t) * p_0 + t * p_1;
auto p_12 = (1 - t) * p_1 + t * p_2;
auto p_23 = (1 - t) * p_2 + t * p_3;

auto p_012 = (1 - t) * p_01 + t * p_12;
auto p_123 = (1 - t) * p_12 + t * p_23;

return cv::Point2f((1 - t) * p_012 + t * p_123);
}
递归算法

另一种递归方式则采用分而治之的策略,将问题不断分化:

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
// TODO: Implement de Casteljau's algorithm
if (control_points.size() == 1)
return control_points[0];

std::vector<cv::Point2f> lines;
for (int i = 0; i < control_points.size() - 1; i++)
lines.emplace_back((1 - t) * control_points[i] + t * control_points[i + 1]);

return recursive_bezier(lines, t);
}

参考文章

❌
❌