首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++-三维转二维,导出DXF文件

C++-三维转二维,导出DXF文件

原创
作者头像
kdyonly
发布2025-09-28 23:13:17
发布2025-09-28 23:13:17
1120
举报
文章被收录于专栏:个人编程笔记个人编程笔记

将三维模型转成二维图纸,需要做的是求出三维模型在某个平面的投影,将获取的投影线,绘制到dxf文件中,即可在二维cad软件使用。

HLR

OCC中的HLR(隐藏线消除)用于在二维屏幕上绘制三维模型时,自动隐藏被遮挡的边或面,只显示可见的部分,使图形更清晰、符合真实视觉效果。

针对有B-Rep(边界表示)模型,可获取可见线,轮廓边,隐藏边,生成用于工程图的“消隐线框图”或“轮廓图”。

参考代码:

代码语言:javascript
复制
Handle(TDocStd_Document) doc;
XCAFApp_Application::GetApplication()->NewDocument("MDTV-XCAF", doc);
Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
if (shapeTool.IsNull()) {
	std::cerr << "ShapeTool not found." << std::endl;
	return 1;
}
shapeTool->AddShape(shape);

TDF_LabelSequence freeShapes;
shapeTool->GetFreeShapes(freeShapes);
std::cout << "Total shapes added: " << freeShapes.Length() << std::endl;

for (Standard_Integer i = 1; i <= freeShapes.Length(); ++i) {
	TDF_Label lab = freeShapes.Value(i);
	TopoDS_Shape shape = shapeTool->GetShape(lab);

	Handle(HLRBRep_Algo) hlr = new HLRBRep_Algo();
	hlr->Add(shape);
	//定义投影视角
	gp_Ax2 view(gp::Origin(), gp::DZ());
	hlr->Projector(HLRAlgo_Projector(view));
	hlr->Update();
	hlr->Hide();
	HLRBRep_HLRToShape hlrToShape(hlr);
	auto visibleEdges = hlrToShape.VCompound(); // 可见边
	auto hiddenEdges = hlrToShape.HCompound(); // 隐藏边
	auto rg1lineEdges = hlrToShape.Rg1LineVCompound();
	auto outlineEdges = hlrToShape.OutLineVCompound();//轮廓线
}

得到我们想要的边之后,然后可以进行dxf文件的写入。参考代码:

代码语言:javascript
复制
std::ofstream dxf("test.dxf");
dxf << "0\\nSECTION\\n2\\nENTITIES\\n";

for (TopExp_Explorer exp(visibleEdges, TopAbs_EDGE); exp.More(); exp.Next()) {
				TopoDS_Edge edge = TopoDS::Edge(exp.Current());
				// 获取顶点
				Standard_Real x1, y1, z1, x2, y2, z2;
				TopoDS_Vertex v1, v2;
				TopExp::Vertices(edge, v1, v2);
				gp_Pnt p1 = BRep_Tool::Pnt(v1);
				gp_Pnt p2 = BRep_Tool::Pnt(v2);
				//if (!silhouetteEdges.Contains(p1, p2) && !silhouetteEdges.Contains(p2, p1)) continue;

				dxf << "0\\nLINE\\n8\\n0\\n";
				dxf << "10\\n" << p1.X() << "\\n20\\n" << p1.Y() << "\\n30\\n" << p1.Z() << "\\n";
				dxf << "11\\n" << p2.X() << "\\n21\\n" << p2.Y() << "\\n31\\n" << p2.Z() << "\\n";
}

dxf << "0\\nENDSEC\\n0\\nEOF\\n";
dxf.close();

自行计算投影线

对于纯网格的三维模型,使用上面的方式不太合适,我们需要先通过面的法向量计算到轮廓边,然后计算轮廓边在指定面的投影。

一条边是轮廓边,当且仅当:在当前视图方向下,其两侧相邻面的法向量相对于视线方向,一个朝向观察者(可见),一个背向观察者(不可见)。

面法向量分别为n1,n2;

视图方向向量为v;

(n1·v)x(n2·v)<0。

如果一条边只有一个面,意味着它是该面的边界,也可以当作轮廓边。

此种实现方式未考虑深度问题,可以加入深度计算,删掉被遮挡的线,得到更加准确的二维图形。

参考代码:

代码语言:javascript
复制
// 顶点 Key
struct PntKey
{
	gp_Pnt p;
	bool operator==(const PntKey& other) const noexcept
	{
		const double eps = 1e-3;
		return std::fabs(p.X() - other.p.X()) < eps &&
			std::fabs(p.Y() - other.p.Y()) < eps &&
			std::fabs(p.Z() - other.p.Z()) < eps;
	}
};

// 自定义哈希
namespace std {
	template<>
	struct hash<PntKey>
	{
		size_t operator()(const PntKey& k) const noexcept
		{
			auto h1 = std::hash<long long>()(llround(k.p.X() * 1e3));
			auto h2 = std::hash<long long>()(llround(k.p.Y() * 1e3));
			auto h3 = std::hash<long long>()(llround(k.p.Z() * 1e3));
			return h1 ^ (h2 << 1) ^ (h3 << 2);
		}
	};
}

struct EdgeKey {
	gp_Pnt p0;
	gp_Pnt p1;
	double eps = 1e-4;

	EdgeKey(const gp_Pnt& a, const gp_Pnt& b) {
		// 保证 p0 < p1,用坐标顺序固定顺序,忽略方向
		if (a.X() < b.X() || (a.X() == b.X() && a.Y() < b.Y()) ||
			(a.X() == b.X() && a.Y() == b.Y() && a.Z() < b.Z())) {
			p0 = a; p1 = b;
		}
		else {
			p0 = b; p1 = a;
		}
	}

	bool operator==(const EdgeKey& other) const noexcept {
		auto eq = [this](const gp_Pnt& a, const gp_Pnt& b) {
			return a.Distance(b) < eps;
			};
		return eq(p0, other.p0) && eq(p1, other.p1);
	}
};

namespace std {
	template <>
	struct hash<EdgeKey> {
		size_t operator()(const EdgeKey& k) const noexcept {
			auto h = [](double x) { return std::hash<long long>()(llround(x * 1e6)); };
			size_t h0 = h(k.p0.X()) ^ (h(k.p0.Y()) << 1) ^ (h(k.p0.Z()) << 2);
			size_t h1 = h(k.p1.X()) ^ (h(k.p1.Y()) << 1) ^ (h(k.p1.Z()) << 2);
			return h0 ^ (h1 << 1);
		}
	};
}

class EdgeSet {
	std::unordered_set<EdgeKey> edges;

public:
	bool Contains(const gp_Pnt& a, const gp_Pnt& b) const {
		return edges.find(EdgeKey(a, b)) != edges.end();
	}

	void Add(const gp_Pnt& a, const gp_Pnt& b) {
		edges.insert(EdgeKey(a, b));
	}

	size_t Size() const { return edges.size(); }
};
代码语言:javascript
复制
bool DxfManager::Write(const std::string& path, std::vector<OsgTest::Mesh> meshes) {
	std::ofstream outlinedxf("outline.dxf");
	outlinedxf << "0\\nSECTION\\n2\\nENTITIES\\n";

	for (const auto& mesh : meshes) {
		//构建边和面的对应
		std::map<Edge, std::vector<int>> edgeToFaces;
		size_t faceId = 0;
		std::vector<Face> facesOut;
		std::vector<int> indices = mesh.indices;
		std::vector<float> positions = mesh.positions;
		std::unordered_map<PntKey, int> tmpIndices;
		std::vector<PntKey> uniquePnts;

		if (indices.size() == 0) {
			for (size_t i = 0; i + 2 < positions.size(); i += 3) {
				float x = positions[i];
				float y = positions[i + 1];
				float z = positions[i + 2];
				gp_Pnt p(x, y, z);
				PntKey pntKey{ p };
				auto it = tmpIndices.find(pntKey);
				if (it != tmpIndices.end())
				{
					// 已存在,直接复用索引
					indices.push_back(it->second);
				}
				else
				{
					uint32_t newIndex = static_cast<uint32_t>(uniquePnts.size());
					uniquePnts.push_back(pntKey);
					tmpIndices[pntKey] = newIndex;
					indices.push_back(newIndex);
				}
			}

			if (indices.size() % 3 != 0) continue;

			for (size_t i = 0; i + 2 < indices.size(); i += 3) {
				try {
					size_t i0 = indices[i];
					size_t i1 = indices[i + 1];
					size_t i2 = indices[i + 2];

					Face f{ {i0, i1, i2} };
					gp_Pnt p1(uniquePnts[i0].p);
					gp_Pnt p2(uniquePnts[i1].p);
					gp_Pnt p3(uniquePnts[i2].p);
					//计算面的法向量
					gp_Vec v1(p1, p2);
					gp_Vec v2(p1, p3);
					gp_Vec n = v1.Crossed(v2);
					if (n.Magnitude() < 1e-10) continue; // 面退化跳过
					f.normal = n;
					facesOut.push_back(f);
					// 三条边
					Edge e1(i0, i1);
					Edge e2(i1, i2);
					Edge e3(i2, i0);
					edgeToFaces[e1].push_back(faceId);
					edgeToFaces[e2].push_back(faceId);
					edgeToFaces[e3].push_back(faceId);
					++faceId;
				}
				catch (const Standard_Failure& e)
				{
					size_t i0 = indices[i];
					size_t i1 = indices[i + 1];
					size_t i2 = indices[i + 2];

					gp_Pnt p1(uniquePnts[i0].p);
					gp_Pnt p2(uniquePnts[i1].p);
					gp_Pnt p3(uniquePnts[i2].p);

					std::cout << p1.X() << p1.Y() << p1.Z() << std::endl;
					std::cout << p2.X() << p2.Y() << p2.Z() << std::endl;
					std::cout << p3.X() << p3.Y() << p3.Z() << std::endl;

					std::cerr << "OCCT exception: " << e.GetMessageString() << std::endl;
					continue;
				}
			}
			std::cout << "总共多少个面:" << faceId << std::endl;
			//计算轮廓边
			gp_Vec viewDir(1, 1, 0);
			gp_Pnt origin(0, 0, 0);
			gp_Vec uDir(0, 0, 1);
			auto projectToPlane = [viewDir, origin, uDir](const gp_Pnt& p) {
				gp_Vec normal = viewDir;
				gp_Vec v(origin, p);
				double t = normal.Dot(v) / normal.SquareMagnitude();
				gp_Pnt proj = p.Translated(-t * normal);
				gp_Dir vDir = normal.Crossed(uDir);
				double u = (proj.XYZ() - origin.XYZ()).Dot(uDir.XYZ());
				double vCoord = (proj.XYZ() - origin.XYZ()).Dot(vDir.XYZ());
				return std::pair<double, double>{u, vCoord};
				};

			std::vector<Edge> silhouetteEdges;
			for (const auto& kv : edgeToFaces) {
				const Edge& edge = kv.first;
				const std::vector<int>& faces = kv.second;

				if (faces.size() == 1) {
					// 自由边 → 轮廓边
					silhouetteEdges.push_back(edge);
				}
				else if (faces.size() == 2) {
					const gp_Vec& n1 = facesOut[faces[0]].normal;
					const gp_Vec& n2 = facesOut[faces[1]].normal;
					double dot1 = n1.Dot(viewDir);
					double dot2 = n2.Dot(viewDir);
					if (dot1 * dot2 < 0) {
						silhouetteEdges.push_back(edge); // 剪影边
					}
					else if ((std::abs(dot1) == 1 && std::abs(dot2) == 0) || (std::abs(dot1) == 0 && std::abs(dot2) == 1)) {
						silhouetteEdges.push_back(edge); // 剪影边
					}
				}
			}
			std::cout << "轮廓边" << silhouetteEdges.size() << std::endl;

			for (const Edge& e : silhouetteEdges)
			{
				gp_Pnt p1(uniquePnts[e.v1].p);
				gp_Pnt p2(uniquePnts[e.v2].p);

				auto [x1, y1] = projectToPlane(p1);
				auto [x2, y2] = projectToPlane(p2);

				outlinedxf << "0\\nLINE\\n8\\n0\\n";
				outlinedxf << "10\\n" << x1 << "\\n20\\n" << y1 << "\\n30\\n0.0\\n";
				outlinedxf << "11\\n" << x2 << "\\n21\\n" << y2 << "\\n31\\n0.0\\n";
			}
		}
	}

	outlinedxf << "0\\nENDSEC\\n0\\nEOF\\n";
	outlinedxf.close();
	return true;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • HLR
  • 自行计算投影线
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档