dxflib是开源的读写dxf文件的库,在QCAD上可以直接下载(QCAD - Downloads)。dxf作为一种通用的绘图文件格式,包含的信息量非常大,要去理解文件的内容也比较受苦,详细的解释在论坛里可以找到(dxf 格式详解_dxf是什么文件格式_runing9的博客-CSDN博客)。使用dxflib库的优势在于它很好地简化了各部分的读写,代码可读性很强,同时配库只需要将压缩包下的src文件包含在目录中即可。在给导师做一个简易的CAD软件时用到了包括dxf文件的读写,利用MFC做可视化,还做了一个尺寸修改的交互。(源码不方便开源,dxflib相关资源已上传,里面有官方的示例,可自行查看)
目录
一、数据结构
在压缩包内的examples/readwrite文件夹下,有写好的接口类test_creationclass,如下图所示(部分已经修改过),从传入的参数类型来理解各种图元信息的数据结构。目前只使用了包括点、线、圆弧、圆、多段线、文本标注和尺寸标注。
Test_CreationClass();
//图元读取
virtual void addLayer(const DL_LayerData& data);
virtual void addPoint(const DL_PointData& data);
virtual void addLine(const DL_LineData& data);
virtual void addArc(const DL_ArcData& data);
virtual void addCircle(const DL_CircleData& data);
virtual void addPolyline(const DL_PolylineData& data);
virtual void addVertex(const DL_VertexData& data);
virtual void add3dFace(const DL_3dFaceData& data);
virtual void addText(const DL_TextData & data);
1.点
struct DXFLIB_EXPORT DL_PointData {
DL_PointData(double px=0.0, double py=0.0, double pz=0.0) {
x = px;
y = py;
z = pz;
}
double x;//3个坐标值
double y;
double z;
};
2.线
struct DXFLIB_EXPORT DL_LineData {
/**
* Constructor.
* Parameters: see member variables.
*/
DL_LineData(double lx1, double ly1, double lz1,
double lx2, double ly2, double lz2) {
x1 = lx1;
y1 = ly1;
z1 = lz1;
x2 = lx2;
y2 = ly2;
z2 = lz2;
}
//2个点坐标
double x1; double y1;double z1;
double x2; double y2; double z2;
};
3.多段线
struct DXFLIB_EXPORT DL_PolylineData {
/**
* Constructor.
* Parameters: see member variables.
*/
DL_PolylineData(int pNumber, int pMVerteces, int pNVerteces, int pFlags, double pElevation = 0.0) {
number = pNumber;
m = pMVerteces;
n = pNVerteces;
elevation = pElevation;
flags = pFlags;
}
unsigned int number;//多段线包含的点数,与DL_VertexData配合使用
unsigned int m;//如果多段线是多边形网格,则m方向上的顶点数
unsigned int n;//如果多段线是多边形网格,则n方向上的顶点数
double elevation;//高程
int flags;
};
struct DXFLIB_EXPORT DL_VertexData {
/**
* Constructor.
* Parameters: see member variables.
*/
DL_VertexData(double px=0.0, double py=0.0, double pz=0.0,
double pBulge=0.0) {
x = px;
y = py;
z = pz;
bulge = pBulge;
}
double x;
double y;
double z;
double bulge;//多段线的顶点凸起部分的定义。使用顶点处的弧角的1/4弧度的切线值,直线为0
};
4.圆弧
struct DXFLIB_EXPORT DL_ArcData {
DL_ArcData(double acx, double acy, double acz,
double aRadius,
double aAngle1, double aAngle2) {
cx = acx;
cy = acy;
cz = acz;
radius = aRadius;
angle1 = aAngle1;
angle2 = aAngle2;
}
//圆弧圆心点坐标
double cx;
double cy;
double cz;
double radius;//圆弧半径
double angle1;//起始角度(以x正半轴逆时针旋转计算角度)
double angle2;//终止角度
};
5.圆
struct DXFLIB_EXPORT DL_CircleData {
DL_CircleData(double acx, double acy, double acz,
double aRadius) {
cx = acx;
cy = acy;
cz = acz;
radius = aRadius;
}
//圆心坐标及半径
double cx;
double cy;
double cz;
double radius;
};
6.文本标注
struct DXFLIB_EXPORT DL_TextData {
DL_TextData(double ipx, double ipy, double ipz,
double apx, double apy, double apz,
double height, double xScaleFactor,
int textGenerationFlags,
int hJustification,
int vJustification,
const std::string& text,
const std::string& style,
double angle)
: ipx(ipx), ipy(ipy), ipz(ipz),
apx(apx), apy(apy), apz(apz),
height(height), xScaleFactor(xScaleFactor),
textGenerationFlags(textGenerationFlags),
hJustification(hJustification),
vJustification(vJustification),
text(text),
style(style),
angle(angle) {
}
//定义点坐标
double ipx;
double ipy;
double ipz;
//对齐点坐标
double apx;
double apy;
double apz;
double height;//字高
double xScaleFactor;//相对X比例因子
int textGenerationFlags;//默认为0,向后为2,倒置为4
int hJustification;//水平对齐
/* 左侧=0,中心=1,右侧=2,对齐=3,中间=4,适应=5
对于3、4、5垂直对齐必须为0 */
int vJustification;//垂直对齐,基线=0,底部=1,中间=2,顶部=3
std::string text;//文本内容
std::string style;//字体
double angle;//旋转角度
};
7.尺寸标注
尺寸标注相对复杂一些,它不止简单的一个文本,还包括直线和箭头,称为标注块,也就是说它需要通过块来添加。具体的内容在文件中查阅,这里不在展示。
virtual void addBlock(const DL_BlockData&data);//添加块
virtual void endBlock();//结束块的添加
virtual void addSolid(const DL_SolidData&data);//箭头
virtual void addMText(const DL_MTextData & data);//标注文本
8.dxf文件数据结构的建立
在官方下载下来的文件中,有一个Test_CreationClass头文件和源文件,dxf文件的数据结构直接用这个类就可以。这个就相当于接口类,在里面可以重写各类图元读取的函数,原本里面是打印相关信息,我改成了保存数据到成员中。需要注意的是多段线的读取结束时需要调用donpolyline,完成多段线的添加,而标注块也是类似的道理,需要读取完这个块。内容见下面头文件以及源文件。
#ifndef TEST_CREATIONCLASS_H
#define TEST_CREATIONCLASS_H
#include<vector>
#include "../src/dl_creationadapter.h"
class Test_CreationClass : public DL_CreationAdapter {
public:
Test_CreationClass();
//图元读取
virtual void addLayer(const DL_LayerData& data);
virtual void addPoint(const DL_PointData& data);
virtual void addLine(const DL_LineData& data);
virtual void addArc(const DL_ArcData& data);
virtual void addCircle(const DL_CircleData& data);
virtual void addPolyline(const DL_PolylineData& data);
virtual void addVertex(const DL_VertexData& data);
virtual void add3dFace(const DL_3dFaceData& data);
virtual void addText(const DL_TextData & data);
//标注块整体读取
bool adddim = false;
virtual void addBlock(const DL_BlockData&data);
virtual void endBlock();
virtual void addSolid(const DL_SolidData&data);
virtual void addMText(const DL_MTextData & data);
void printAttributes();
void donepolyline()
{
for (int i = 0; i < m_polylines.size(); ++i)
m_polylines[i].first.number = m_polylines[i].second.size();
}
class dimblock{
public:
dimblock(){
dimlines.clear();
dimtext.clear();
dimarrow.clear();
}
std::vector<DL_LineData>dimlines;
std::vector<DL_MTextData> dimtext;
std::vector<DL_SolidData>dimarrow;
};
public://图元信息
//DL_LayerData m_layerdata; //层
std::vector<DL_PointData>m_points; //点
std::vector<DL_LineData>m_lines; //直线
std::vector<std::pair<DL_PolylineData, std::vector<DL_VertexData>>>m_polylines; //多段线
std::vector<DL_ArcData> m_arcs; //圆弧
std::vector<DL_CircleData >m_circles; //圆
std::vector<DL_SolidData>m_solid; //实体
std::vector<DL_TextData>m_text; //普通文本
std::vector<DL_MTextData>m_mtext; //标注文本
std::vector<dimblock>m_dimblock; //标注块
};
#endif
#include "test_creationclass.h"
#include <iostream>
#include <stdio.h>
/**
* Default constructor.
*/
Test_CreationClass::Test_CreationClass() {
m_points.clear();
m_lines.clear();
m_polylines.clear();
m_circles.clear();
m_arcs.clear();
m_text.clear();
m_mtext.clear();
m_dimblock.clear();
}
/**
* Sample implementation of the method which handles layers.
*/
void Test_CreationClass::addLayer(const DL_LayerData& data) {
// printf("LAYER: %s flags: %d\n", data.name.c_str(), data.flags);
// printAttributes();
}
/**
* Sample implementation of the method which handles point entities.
*/
void Test_CreationClass::addPoint(const DL_PointData& data) {
m_points.push_back(data);
// printf("POINT (%6.3f, %6.3f, %6.3f)\n",data.x, data.y, data.z);
// printAttributes();
}
/**
* Sample implementation of the method which handles line entities.
*/
void Test_CreationClass::addLine(const DL_LineData& data) {
if (adddim)
m_dimblock[m_dimblock.size() - 1].dimlines.push_back(data);
else
m_lines.push_back(data);
// printf("LINE (%6.3f, %6.3f, %6.3f) (%6.3f, %6.3f, %6.3f)\n",data.x1, data.y1, data.z1, data.x2, data.y2, data.z2);
// printAttributes();
}
/**
* Sample implementation of the method which handles arc entities.
*/
void Test_CreationClass::addArc(const DL_ArcData& data) {
m_arcs.push_back(data);
// printf("ARC (%6.3f, %6.3f, %6.3f) %6.3f, %6.3f, %6.3f\n",data.cx, data.cy, data.cz, data.radius, data.angle1, data.angle2);
// printAttributes();
}
/**
* Sample implementation of the method which handles circle entities.
*/
void Test_CreationClass::addCircle(const DL_CircleData& data) {
m_circles.push_back(data);
// printf("CIRCLE (%6.3f, %6.3f, %6.3f) %6.3f\n",data.cx, data.cy, data.cz, data.radius);
// printAttributes();
}
/**
* Sample implementation of the method which handles polyline entities.
*/
void Test_CreationClass::addPolyline(const DL_PolylineData& data) {
std::vector<DL_VertexData> temp; temp.clear();
std::pair<DL_PolylineData, std::vector<DL_VertexData>> newone(data,temp);
m_polylines.push_back(newone);
// printf("POLYLINE \n");
// printf("flags: %d\n", (int)data.flags);
// printAttributes();
}
/**
* Sample implementation of the method which handles vertices.
*/
void Test_CreationClass::addVertex(const DL_VertexData& data) {
m_polylines[m_polylines.size() - 1].second.push_back(data);
// printf("VERTEX (%6.3f, %6.3f, %6.3f) %6.3f\n",data.x, data.y, data.z,data.bulge);
// printAttributes();
}
void Test_CreationClass::add3dFace(const DL_3dFaceData& data) {
printf("3DFACE\n");
// for (int i=0; i<4; i++) {
// printf(" corner %d: %6.3f %6.3f %6.3f\n", i, data.x[i], data.y[i], data.z[i]);
// }
// printAttributes();
}
void Test_CreationClass::addBlock(const DL_BlockData&data)
{
adddim = true;
dimblock newone;
m_dimblock.push_back(newone);
}
void Test_CreationClass::endBlock()
{
adddim = false;
if (m_dimblock[m_dimblock.size() - 1].dimlines.size() < 1
&& m_dimblock[m_dimblock.size() - 1].dimtext.size() < 1
&& m_dimblock[m_dimblock.size() - 1].dimarrow.size()<1)
m_dimblock.pop_back();
}
void Test_CreationClass::addSolid(const DL_SolidData&data)
{
if (adddim)
m_dimblock[m_dimblock.size() - 1].dimarrow.push_back(data);
else
m_solid.push_back(data);
}
void Test_CreationClass::addText(const DL_TextData & data)
{
m_text.push_back(data);
}
void Test_CreationClass::addMText(const DL_MTextData & data)
{
if (adddim)
m_dimblock[m_dimblock.size() - 1].dimtext.push_back(data);
else
m_mtext.push_back(data);
}
void Test_CreationClass::printAttributes() {
// printf(" Attributes: Layer: %s, ", attributes.getLayer().c_str());
// printf(" Color: ");
// if (attributes.getColor()==256) {
// printf("BYLAYER");
// } else if (attributes.getColor()==0) {
// printf("BYBLOCK");
// } else {
// printf("%d", attributes.getColor());
// }
// printf(" Width: ");
// if (attributes.getWidth()==-1) {
// printf("BYLAYER");
// } else if (attributes.getWidth()==-2) {
// printf("BYBLOCK");
// } else if (attributes.getWidth()==-3) {
// printf("DEFAULT");
// } else {
// printf("%d", attributes.getWidth());
// }
// printf(" Type: %s\n", attributes.getLinetype().c_str());
}
// EOF
二、读dxf文件
在外部类中声明上述的类Test_CreationClass为成员变量,定义一个读文件的方法。例如我是外部又建立了一个MyDxf类用来衔接MFC的button事件或其他控件,注意包含相关的头文件。
public:
Test_CreationClass * m_data; //dxf数据输出接口
void MyDxf::OpenDxf_Button(string filePath)
{
// Load DXF file into memory:
Initialdxf();
std::cout << "Reading file " << filePath << "...\n";
if (!m_dxf->in(filePath, m_data)) { // if file open failed
std::cerr << filePath << " could not be opened.\n";
return;
}
m_data->donepolyline();
}
三、写dxf文件
说是写文件,其实是保存文件,写文件的过程就是对m_data修改的过程,例如后面添加交互功能,画线,画圆之类的,只需往相应的m_data中的容器添加即可。
void MyDxf::SaveDxf_Button(string filePath)
{
if (m_data==NULL)return;
FILE *pfd = fopen(filePath.c_str(), "w+");
DL_Codes::version exportVersion = DL_Codes::AC1015;
DL_WriterA* dw = m_dxf->out(filePath.c_str(), exportVersion);
if (dw == NULL) { printf("Cannot open file 'myfile.m_dxf' \ for writing."); return; }
/*--------------------------------------------------------*/
//1 标题段
m_dxf->writeHeader(*dw);
dw->sectionEnd();
/*--------------------------------------------------------*/
//2 表段
dw->sectionTables();
m_dxf->writeVPort(*dw);
//2.1 线型表(Linetype)
dw->tableLinetypes(3);
m_dxf->writeLinetype(*dw, DL_LinetypeData("BYBLOCK", "BYBLOCK", 0, 0, 0.0));
m_dxf->writeLinetype(*dw, DL_LinetypeData("BYLAYER", "BYLAYER", 0, 0, 0.0));
m_dxf->writeLinetype(*dw, DL_LinetypeData("CONTINUOUS", "Continuous", 0, 0, 0.0));
dw->tableEnd();
//2.2 图层表(Layer)
int numberOfLayers = 3;//层数3
dw->tableLayers(numberOfLayers);
m_dxf->writeLayer(*dw, DL_LayerData("0", 0), DL_Attributes(std::string(""), DL_Codes::black, 100, "CONTINUOUS", 1.0));
m_dxf->writeLayer(*dw, DL_LayerData("mainlayer", 0), DL_Attributes(std::string(""), DL_Codes::black, 100, "CONTINUOUS", 1.0));
m_dxf->writeLayer(*dw, DL_LayerData("anotherlayer", 0), DL_Attributes(std::string(""), DL_Codes::blue, 100, "CONTINUOUS", 1.0));
dw->tableEnd();
//2.3 字样表(Style)
dw->tableStyle(1);
m_dxf->writeStyle(*dw, DL_StyleData("standard", 0, 2.5, 1.0, 0.0, 0, 2.5, "txt", ""));
dw->tableEnd();
//2.4 视图表(View)
m_dxf->writeView(*dw);
m_dxf->writeUcs(*dw);
dw->tableAppid(1);
m_dxf->writeAppid(*dw, "ACAD");
dw->tableEnd();
//2.5 DL_VERSION_R13中需要伪造dimstyle和blockrecord两部分,以使AutoCAD能够读取该文件。
m_dxf->writeDimStyle(*dw, 1, 1, 1, 1, 1);
m_dxf->writeBlockRecord(*dw);
dw->tableEnd();
dw->sectionEnd();
/*--------------------------------------------------------*/
//3 块段
DL_Attributes typetemp1("mainlayer", 256, -1, "BYLAYER", 1.0);//属性
DL_Attributes typetemp2("anotherlayer", 256, -1, "BYLAYER", 1.0);//属性
dw->sectionBlocks();
m_dxf->writeBlock(*dw, DL_BlockData("*Model_Space", 0, 0.0, 0.0, 0.0));
m_dxf->writeEndBlock(*dw, "*Model_Space");
m_dxf->writeBlock(*dw, DL_BlockData("*Paper_Space", 0, 0.0, 0.0, 0.0));
m_dxf->writeEndBlock(*dw, "*Paper_Space");
m_dxf->writeBlock(*dw, DL_BlockData("*Paper_Space0", 0, 0.0, 0.0, 0.0));
m_dxf->writeEndBlock(*dw, "*Paper_Space0");
// m_dxf->writeBlock(*dw, DL_BlockData("myblock1", 0, 0.0, 0.0, 0.0));
// // ...
// // write block entities e.g. with m_dxf->writeLine(), ..
// // ...
// m_dxf->writeEndBlock(*dw, "myblock1");
dw->sectionEnd();
/*--------------------------------------------------------*/
//4 实体段
dw->sectionEntities();
for (int i = 0; i < m_data->m_points.size(); ++i)
{
m_dxf->writePoint(*dw, m_data->m_points[i], typetemp1);
}
for (int i = 0; i < m_data->m_lines.size(); ++i)
{
m_dxf->writeLine(*dw, m_data->m_lines[i], typetemp1);
}
for (int i = 0; i < m_data->m_polylines.size(); ++i)
{
m_dxf->writePolyline(*dw, m_data->m_polylines[i].first, typetemp1);
for (int j = 0; j < m_data->m_polylines[i].second.size(); ++j)
{
m_dxf->writeVertex(*dw, m_data->m_polylines[i].second[j]);
}
m_dxf->writePolylineEnd(*dw);
}
for (int i = 0; i < m_data->m_arcs.size(); ++i)
{
m_dxf->writeArc(*dw, m_data->m_arcs[i], typetemp1);
}
for (int i = 0; i < m_data->m_circles.size(); ++i)
{
m_dxf->writeCircle(*dw, m_data->m_circles[i], typetemp1);
}
for (int i = 0; i < m_data->m_text.size();++i)
{
//m_dxf->writeText(*dw, m_data->m_text[i], typetemp2);//用text写有问题,使用MText代替
DL_MTextData temp(m_data->m_text[i].ipx, m_data->m_text[i].ipy, m_data->m_text[i].ipz,
0, 0, 0, m_data->m_text[i].height, 0, 5, 1, 1, 1, m_data->m_text[i].text, m_data->m_text[i].style, m_data->m_text[i].angle);
m_dxf->writeMText(*dw, temp, typetemp2);
}
for (int i = 0; i < m_data->m_mtext.size(); ++i)
{
//m_data->m_mtext[i].height = 30;
DL_MTextData temp = m_data->m_mtext[i];
temp.height = 50;
m_dxf->writeMText(*dw, temp, typetemp2);
}
dw->sectionEnd();
/*--------------------------------------------------------*/
//5 文件结束段
m_dxf->writeObjects(*dw);
m_dxf->writeObjectsEnd(*dw);
dw->dxfEOF();
dw->close();
delete dw;
delete m_dxf;
dw = nullptr;
m_dxf = nullptr;
}
四、可视化
在MFC框架中,有一个Doc类和View类,在Doc类中将我们的外部接口类MyDxf作为成员变量,注意在构造里初始化,在析构里释放。顾名思义,在Doc类中,要做的操作是数据的导入导出,也就是dxf文件的读写,而View类中去显示m_data中的数据。在对数据可视化之前,首先介绍一下文件的读写接口,不然怎么读数据来可视化呢。
public:
MyDxf *m_mydxf;
首先,在资源视图中,找到菜单栏,接着在打开的标签页中添加事件触发的菜单,这里主要就是打开按钮和另存为按钮。注意在右下角的属性里面设置控件的ID,当然默认也行,记住就行。然后鼠标放在打开按钮上,右键点击添加事件响应程序,然后找到选择Doc类,添加编辑,这里我是已经添加过了,所以显示编辑代码。添加编辑后会自动跳转到目标函数体内。后面是打开,保存dxf文件的代码。
void CDXFminiDoc::OnOpendxf()
{
// TODO: 在此添加命令处理程序代码
CString defaultDir = _T("C:/Users/csy/Desktop");//默认打开的文件路径
CString filter = _T("文件 (*.dxf)|*.dxf||");//文件过虑的类型
CFileDialog dlg(TRUE, defaultDir, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter, NULL);
if (dlg.DoModal() == IDOK)
{
CString filePath = dlg.GetPathName();
m_mydxf = new MyDxf();
string str = CT2A(filePath.GetString());
m_mydxf->OpenDxf_Button(str);
}
UpdateAllViews(NULL);
}
void CDXFminiDoc::OnSavedxf()
{
// TODO: 在此添加命令处理程序代码
if (!m_mydxf)return;
CString defaultDir = _T("C:/Users/lb/Desktop");//默认路径
CString fileName = _T("newdoor.dxf");//文件名
CString filter = _T("文件 (*.dxf)|*.dxf||");//文件过滤类型
CFileDialog dlg(FALSE, defaultDir, fileName, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter, NULL);
if (dlg.DoModal() == IDOK)
{
CString filePath = dlg.GetPathName();
string str = CT2A(filePath.GetString());
m_mydxf->SaveDxf_Button(str);
}
UpdateAllViews(NULL);
}
其次,MFC提供了封装好的绘制函数,调用就行,在view类的OnDraw函数里,放置我们总的绘制函数drawdxf,将所有图元的绘制调用都放到它里面去。这里只展示画多段线的方法
void CDXFminiView::OnDraw(CDC* pDC)
{
CDXFminiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)return;
if (!pDoc->m_mydxf)return;
else {
GetClientRect(&rect);
CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
MemDC.CreateCompatibleDC(NULL);//建立与屏幕显示兼容的内存显示设备
MemBitmap.CreateCompatibleBitmap(pDC, rect.right - rect.left, rect.bottom - rect.top );//建立与屏幕显示兼容的位图
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);//将位图选入到内存显示设备中,只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
MemDC.FillSolidRect(0, 0, rect.right - rect.left, rect.bottom - rect.top, RGB(255, 255, 255));//清空位图,以白色填充背景
pDoc->m_mydxf->drawdxf(&MemDC, rect.right, rect.bottom);//绘制dxf
pDC->BitBlt(0, 0, rect.right - rect.left, rect.bottom - rect.top, &MemDC, 0, 0, SRCCOPY);//将内存中的图拷贝到屏幕上进行显示
MemBitmap.DeleteObject();//绘图完成后的清理
MemDC.DeleteDC();
}
}
void MyDxf::drawdxf(CDC *pDC,double dx,double dy)
{
if (!m_data)return;
if (firstdraw)
{
m_dxfbbox=GetDxfRect(m_data);
InitialViewMtrx(dx, dy);
chozenp.clear();
}
firstdraw = false;
drawpoint(pDC);
drawlines(pDC);
drawpolylines(pDC);
drawarc(pDC);
drawcircle(pDC);
drawtext(pDC);
//drawmtext(pDC);
drawdimblock(pDC);
drawchoosemtext(pDC, chozenp);
}
void MyDxf::drawpolylines(CDC *pDC)
{
CPen NewPen, *pOldPen;
NewPen.CreatePen(PS_DOT, 2, RGB(0, 0, 0));
pOldPen = pDC->SelectObject(&NewPen);
for (int i = 0; i < m_data->m_polylines.size(); i++)
{
if (m_data->m_polylines[i].second.size() <= 1)break;
for (int j = 0; j < m_data->m_polylines[i].second.size() - 1; ++j)
{
CPoint2D p1(m_data->m_polylines[i].second[j].x, m_data->m_polylines[i].second[j].y);
CPoint2D p2(m_data->m_polylines[i].second[j + 1].x, m_data->m_polylines[i].second[j + 1].y);
p1 = p1*m_ViewMtrx; p2 = p2*m_ViewMtrx;
pDC->MoveTo(p1.x,p1.y);
pDC->LineTo(p2.x,p2.y);
}
}
pDC->SelectObject(pOldPen);
NewPen.DeleteObject();
}
五、视图变换与交互
视图变换首先要了解一下二维坐标的基本变换,可以看我的博客【二维坐标基本变换】。交互这里只介绍视图的交互,左键拖动视图,右键重置默认视图,鼠标滚轮实现视图的放大缩小。对于拖动视图,原本应该涉及到3个鼠标响应事件:鼠标点下,移动和抬起,但实际上用后2个就足够了,在鼠标移动事件中添加一个flag,识别鼠标左键是否处于按下的状态MK_LBUTTON。
void CDXFminiView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
this->SetFocus();
CDXFminiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
bool flag =(nFlags &MK_LBUTTON);
if (pDoc->m_mydxf != NULL&&flag)
{
CPoint2D sp(point.x, point.y);
pDoc->m_mydxf->TransformViewMtrx(sp);
Invalidate();
}
CView::OnMouseMove(nFlags, point);
}
void CDXFminiView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CDXFminiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
bool flag = (nFlags &MK_LBUTTON);
if (pDoc->m_mydxf != NULL&&!flag)
pDoc->m_mydxf->m_movepoint = pDoc->m_mydxf->m_ScreenCenter;
CView::OnLButtonUp(nFlags, point);
}
void CDXFminiView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CDXFminiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (pDoc->m_mydxf != NULL&&nFlags)
{
pDoc->m_mydxf->InitialViewMtrx(rect.right, rect.bottom);
Invalidate();
}
CView::OnRButtonDown(nFlags, point);
}
BOOL CDXFminiView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CDXFminiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
/*bool flag = nFlags;*/
if (pDoc->m_mydxf != NULL/*&&flag*/)
{
float scale0 =1.0+ 0.1*zDelta / 50;
pDoc->m_mydxf->ZoomViewMtrx(scale0);
Invalidate();
}
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
这里以缩放函数为例说明一下如何连接到视图更新的,在该函数里我们修改的是m_ViewMtrx这个矩阵,注意在绘制函数中,待绘制的点都会乘上这一个矩阵,同样的移动和重置函数也是通过更新这个矩阵来连接的。
void MyDxf::ZoomViewMtrx(float &t)
{
m_zoomtimes *= t;
CMatrix2D transfMtrx1, transfMtrx2, zoomMtrx;
VECTOR2D t2, t3;
t3.dx = m_dxfbbox.cx;
t3.dy = m_dxfbbox.cy;
t2.dx = -t3.dx;
t2.dy = -t3.dy;
transfMtrx1 = transfMtrx1.CreateTransfMatrix(t2);
transfMtrx2 = transfMtrx2.CreateTransfMatrix(t3);
zoomMtrx = zoomMtrx.CreateScaleMatrix(t);
m_ViewMtrx = transfMtrx1*zoomMtrx*transfMtrx2*m_ViewMtrx;
}
最后实现的效果如下:
当然,这个小应用还有很多不足,比如直径符号,文本的位置等等,尚待研究。