将三维模型转成二维图纸,需要做的是求出三维模型在某个平面的投影,将获取的投影线,绘制到dxf文件中,即可在二维cad软件使用。
OCC中的HLR(隐藏线消除)用于在二维屏幕上绘制三维模型时,自动隐藏被遮挡的边或面,只显示可见的部分,使图形更清晰、符合真实视觉效果。
针对有B-Rep(边界表示)模型,可获取可见线,轮廓边,隐藏边,生成用于工程图的“消隐线框图”或“轮廓图”。
参考代码:
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文件的写入。参考代码:
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。
如果一条边只有一个面,意味着它是该面的边界,也可以当作轮廓边。
此种实现方式未考虑深度问题,可以加入深度计算,删掉被遮挡的线,得到更加准确的二维图形。
参考代码:
// 顶点 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(); }
};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 删除。