From af23f908c98691a2eda4c03eda5aa0193c2dfbc5 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Fri, 22 May 2026 17:01:07 +0800 Subject: [PATCH 01/21] =?UTF-8?q?feat(gmsh):=E5=88=9D=E5=A7=8B=E5=8C=96gms?= =?UTF-8?q?h=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 20 + plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 175 ++++ plugins/algo/GmshPlugin/GmshMeshHandler.h | 15 + plugins/algo/GmshPlugin/GmshMeshPlugin.h | 19 + plugins/algo/GmshPlugin/GmshPlugin.json | 7 + .../algo/GmshPlugin/IncrementalMeshTools.cpp | 972 ++++++++++++++++++ .../algo/GmshPlugin/IncrementalMeshTools.h | 39 + plugins/algo/GmshPlugin/test/CMakeLists.txt | 33 + .../algo/GmshPlugin/test/GmshPluginTest.cpp | 78 ++ .../algo/GmshPlugin/test/GmshVtkExample.cpp | 215 ++++ 10 files changed, 1573 insertions(+) create mode 100644 plugins/algo/GmshPlugin/CMakeLists.txt create mode 100644 plugins/algo/GmshPlugin/GmshMeshHandler.cpp create mode 100644 plugins/algo/GmshPlugin/GmshMeshHandler.h create mode 100644 plugins/algo/GmshPlugin/GmshMeshPlugin.h create mode 100644 plugins/algo/GmshPlugin/GmshPlugin.json create mode 100644 plugins/algo/GmshPlugin/IncrementalMeshTools.cpp create mode 100644 plugins/algo/GmshPlugin/IncrementalMeshTools.h create mode 100644 plugins/algo/GmshPlugin/test/CMakeLists.txt create mode 100644 plugins/algo/GmshPlugin/test/GmshPluginTest.cpp create mode 100644 plugins/algo/GmshPlugin/test/GmshVtkExample.cpp diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt new file mode 100644 index 0000000..b7a87c9 --- /dev/null +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -0,0 +1,20 @@ +find_package(gmsh REQUIRED) + +precess_add_algo_plugin(GmshPlugin + SOURCES + "GmshMeshHandler.cpp" + "IncrementalMeshTools.cpp" + PLUGIN_H "GmshMeshPlugin.h" +) +precess_plugin_link_libraries(GmshPlugin + gmsh::shared + TKBRep # BRep_Builder, BRepTools + TKernel # Standard_* 基础类型 + TKG3d # gp_* 准则基础 + TKDESTEP # STEPControl_Reader + TKXSBase # STEP/IGES 转换基础 + TKTopAlgo # TopExp_Explorer +) + + +add_subdirectory(test) \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp new file mode 100644 index 0000000..5dbab11 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -0,0 +1,175 @@ +#include "GmshMeshHandler.h" +#include "ModelOperatorBase.h" +#include "ArgObject.h" +#include "IncrementalMeshContext.h" +#include "IncrementalMeshTools.h" +#include "ModelData.h" +#include "ModelIOSystemBase.h" +#include "SplineData.h" +#include "ModelOperator.h" +#include "MeshData.h" +#include + +#include +#include +#include + +using core::ArgType; + +std::any systems::algo::GmshMeshHandler::execute( + HandlerContext& context, + const std::vector& args) +{ + // 解析参数:面索引 + 网格尺寸 + 操作类型 + int faceIndex = -1; + double meshSize = 0.0; + int operationMode = 1; // 默认 1:网格划分 + + if (args.size() >= 1) { + const std::string* idxStr = args[0].get(); + if (idxStr && !idxStr->empty()) { + try { + faceIndex = std::stoi(*idxStr); + } catch (...) { + spdlog::warn("GmshMesh: invalid face index '{}'", *idxStr); + } + } + } + if (args.size() >= 2) { + const std::string* sizeStr = args[1].get(); + if (sizeStr && !sizeStr->empty()) { + try { + meshSize = std::stod(*sizeStr); + } catch (...) { + spdlog::warn("GmshMesh: invalid size '{}'", *sizeStr); + } + } + } + if (args.size() >= 3) { + const std::string* opStr = args[2].get(); + if (opStr && !opStr->empty()) { + try { + operationMode = std::stoi(*opStr); + } catch (...) { + spdlog::warn("GmshMesh: invalid operation mode '{}', using default 1 (Mesh)", *opStr); + } + } + } + + if (faceIndex < 0) { + spdlog::error("GmshMesh: need face id"); + return {}; + } + + // 获取 SplineData + if (context.cur_model.getType() != ModelData::Type::Spline) { + spdlog::error("GmshMesh: need Spline model"); + return {}; + } + ModelOperator* op = dynamic_cast(&context.cur_model); + if (!op) { + spdlog::error("GmshMesh: cant get ModelOperator"); + return {}; + } + + SplineData* sp = op->data().asSplineData(); + if (!sp || !sp->rootShape) { + spdlog::error("GmshMesh: SplineData is illegal"); + return {}; + } + + // 获取 MeshData + MeshData* mesh_data = op->data().getMeshData(); + if (!mesh_data) { + spdlog::info("GmshMesh: mesh_data is null, create a new one"); + auto new_mesh = std::make_unique(); + new_mesh->init(); + op->data().setMeshData(std::move(new_mesh)); + mesh_data = op->data().getMeshData(); + if (!mesh_data) { + spdlog::error("GmshMesh: failed to create mesh_data"); + return {}; + } + } + + // 首次调用:初始化拓扑索引 + if (!sp->meshContext) { + spdlog::info("GmshMesh: init occId..."); + sp->meshContext = std::make_unique(*sp->rootShape); + spdlog::info("GmshMesh: {} face, {} global edge", + sp->meshContext->faceCount(), + sp->meshContext->globalEdgeCount()); + } + + // 检查面索引范围 + std::size_t totalFaces = sp->meshContext->faceCount(); + if (static_cast(faceIndex) >= totalFaces) { + spdlog::error("GmshMesh: face id {} out of range (total {} face)", + faceIndex, totalFaces); + return {}; + } + + if (meshSize <= 0.0) + meshSize = IncrementalMeshTools::estimateMeshSize(*sp); + + SingleFaceMeshResult result; + + if (operationMode == 2) { + // 删除网格 + spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); + if (IncrementalMeshTools::deleteFaceMesh(*mesh_data, *sp, static_cast(faceIndex))) + spdlog::info("GmshMesh: delete face {} sucess", faceIndex); + } + else if (operationMode == 3) { + // 重划分网格 + spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); + result = IncrementalMeshTools::remeshSingleFace( + *mesh_data, *sp, static_cast(faceIndex), meshSize); + } + else { + // 划分网格 (默认) + spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); + result = IncrementalMeshTools::meshSingleFace( + *mesh_data, *sp, static_cast(faceIndex), meshSize); + } + + if (operationMode != 2) { + if (!result.success) { + spdlog::warn("GmshMesh: face {} meshing failed", faceIndex); + return {}; + } + + spdlog::info("GmshMesh:face {} finish , {} nodes, {} cells , cached {} edge", + faceIndex, + result.vertices.size(), + result.face_vertices_offset.size() - 1, + sp->meshedEdgeRefCounts.size()); + + // 单面输出为obj + std::string face_out = core::TempFile::instance().path().string() +"_single_face_" + std::to_string(faceIndex) + ".obj"; + if (!IncrementalMeshTools::writeSingleFaceObj(result, face_out)) { + spdlog::error("GmshMesh: cant save single face "); + return {}; + } + context.io_system.read(face_out, "Wavefront .obj file", {}); + } + + // 将总的mesh_data写出为obj + std::string mesh_out = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; + if (!IncrementalMeshTools::writeMeshObj(*mesh_data, mesh_out)) { + spdlog::error("GmshMesh: cant save meshdata "); + return {}; + } + context.io_system.read(mesh_out, "Wavefront .obj file", {}); + + return {}; +} + +std::vector systems::algo::GmshMeshHandler::args_type() const +{ + return { + ArgType { ArgTypeEnum::Text, "面索引(0开始)", "" }, + ArgType { ArgTypeEnum::Text, "网格尺寸(留空自动)", "" }, + ArgType { ArgTypeEnum::Text, "1:mesh 2:delete 3:remesh", "" } + }; +} \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.h b/plugins/algo/GmshPlugin/GmshMeshHandler.h new file mode 100644 index 0000000..17355d2 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.h @@ -0,0 +1,15 @@ +#pragma once +#include "AlgorithmHandler.h" +#include +#include + +namespace systems::algo { +class GmshMeshHandler : public AlgorithmHandler { +public: + GmshMeshHandler() = default; + ~GmshMeshHandler() override = default; + + std::any execute(HandlerContext& context, const std::vector& args) override; + std::vector args_type() const override; +}; +} // namespace systems::algo \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/GmshMeshPlugin.h b/plugins/algo/GmshPlugin/GmshMeshPlugin.h new file mode 100644 index 0000000..38dea1c --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshMeshPlugin.h @@ -0,0 +1,19 @@ +#pragma once +#include "PluginBase.h" +#include "GmshMeshHandler.h" +#include "HandlerCreatorDestroyerFactory.h" +#include + +namespace systems::algo { +class GmshMeshPlugin : public QObject, public PluginBase { + Q_OBJECT + Q_INTERFACES(systems::PluginBase) + Q_PLUGIN_METADATA(IID "com.PreCess.systems.algo.GmshMeshPlugin/1.0" + FILE "GmshPlugin.json") +private: + const HandlerCreatorDestroyer& getHandlerCreatorDestroyer() noexcept override final + { + return HandlerCreatorDestroyerFactory::get(); + } +}; +} // namespace systems::algo \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/GmshPlugin.json b/plugins/algo/GmshPlugin/GmshPlugin.json new file mode 100644 index 0000000..3323ba7 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshPlugin.json @@ -0,0 +1,7 @@ +{ + "system": "AlgorithmSystem", + "handler": { + "name": "GmshPlugin", + "display_name": "Gmsh渐进式划分网格" + } +} \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp new file mode 100644 index 0000000..c231fa2 --- /dev/null +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -0,0 +1,972 @@ +#include "IncrementalMeshTools.h" +#include "IncrementalMeshContext.h" + +#include "MeshData.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +// 合并网格时的顶点去重 +class TempNodeLookup { +public: + explicit TempNodeLookup( + std::vector>& vertices, + double tolerance = 1e-7) + : _vertices(vertices) + , _tolerance(tolerance) + { + for (size_t i = 0; i < _vertices.size(); ++i) { + auto qc = _quantize(_vertices[i][0], _vertices[i][1], _vertices[i][2]); + _map[qc] = i; + } + } + + size_t getOrInsert(double x, double y, double z) + { + auto qc = _quantize(x, y, z); + auto it = _map.find(qc); + if (it != _map.end()) + return it->second; + + size_t idx = _vertices.size(); + _vertices.push_back({ x, y, z }); + _map[qc] = idx; + return idx; + } + +private: + struct QuantizedCoord { + int64_t ix, iy, iz; + bool operator==(const QuantizedCoord& o) const + { + return ix == o.ix && iy == o.iy && iz == o.iz; + } + }; + + struct CoordHash { + size_t operator()(const QuantizedCoord& c) const + { + size_t h = 0; + h ^= std::hash()(c.ix) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash()(c.iy) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash()(c.iz) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } + }; + + QuantizedCoord _quantize(double x, double y, double z) const + { + return { + static_cast(std::round(x / _tolerance)), + static_cast(std::round(y / _tolerance)), + static_cast(std::round(z / _tolerance)) + }; + } + + std::vector>& _vertices; + double _tolerance; + std::unordered_map _map; +}; + +// ---- 加载 STEP ---- +TopoDS_Shape loadStep(const std::string& path) +{ + STEPControl_Reader reader; + if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) { + spdlog::error("Cannot read STEP: {}", path); + return {}; + } + reader.TransferRoots(); + return reader.OneShape(); +} + +// ---- 包 Compound ---- +TopoDS_Compound makeFaceCompound(const TopoDS_Face& face) +{ + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + builder.Add(compound, face); + return compound; +} + +// ---- 边几何特征 ---- +struct EdgeGeoFeature { + // 两端点 + double x1, y1, z1; + double x2, y2, z2; + + // 曲线中点 + double mx, my, mz; + + // 长度 + double length = 0.0; + + // 3 个采样点 + std::vector samples; // [x0,y0,z0, x1,y1,z1, x2,y2,z2] + + static double pointDist(double ax, double ay, double az, + double bx, double by, double bz) + { + double dx = ax - bx; + double dy = ay - by; + double dz = az - bz; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + // 无向端点距离:考虑方向相反的情况 + double endpointDistanceTo(const EdgeGeoFeature& o) const + { + double dForward = pointDist(x1, y1, z1, o.x1, o.y1, o.z1) + pointDist(x2, y2, z2, o.x2, o.y2, o.z2); + + double dReverse = pointDist(x1, y1, z1, o.x2, o.y2, o.z2) + pointDist(x2, y2, z2, o.x1, o.y1, o.z1); + + return std::min(dForward, dReverse); + } + + double midpointDistanceTo(const EdgeGeoFeature& o) const + { + return pointDist(mx, my, mz, o.mx, o.my, o.mz); + } + + double lengthDistanceTo(const EdgeGeoFeature& o) const + { + return std::abs(length - o.length); + } + + // 采样点距离,同样考虑正向/反向 + double sampleDistanceTo(const EdgeGeoFeature& o) const + { + if (samples.size() != o.samples.size() || samples.empty()) + return 0.0; + + std::size_t n = samples.size() / 3; + double dForward = 0.0; + double dReverse = 0.0; + + for (std::size_t i = 0; i < n; ++i) { + std::size_t i3 = i * 3; + std::size_t r3 = (n - 1 - i) * 3; + + dForward += pointDist( + samples[i3], samples[i3 + 1], samples[i3 + 2], + o.samples[i3], o.samples[i3 + 1], o.samples[i3 + 2]); + + dReverse += pointDist( + samples[i3], samples[i3 + 1], samples[i3 + 2], + o.samples[r3], o.samples[r3 + 1], o.samples[r3 + 2]); + } + + return std::min(dForward, dReverse); + } + + // 综合评分 + double distanceTo(const EdgeGeoFeature& o, double scale) const + { + // 防止 scale 太小 + double s = std::max(scale, 1e-12); + + double de = endpointDistanceTo(o) / s; + double dl = lengthDistanceTo(o) / s; + double dm = midpointDistanceTo(o) / s; + double ds = sampleDistanceTo(o) / s; + + // 端点权重大一些 + return 5.0 * de + 2.0 * dl + 3.0 * dm + 4.0 * ds; + } +}; + +std::vector sampleOCCEdgePoints(const TopoDS_Edge& edge, int sampleCount = 3) +{ + std::vector pts; + if (sampleCount <= 0) + return pts; + + BRepAdaptor_Curve curve(edge); + double u1 = curve.FirstParameter(); + double u2 = curve.LastParameter(); + + for (int i = 1; i <= sampleCount; ++i) { + double t = double(i) / double(sampleCount + 1); + double u = (1.0 - t) * u1 + t * u2; + gp_Pnt p = curve.Value(u); + pts.push_back(p.X()); + pts.push_back(p.Y()); + pts.push_back(p.Z()); + } + return pts; +} +std::vector sampleGmshEdgePoints(int gmshTag, int sampleCount = 3) +{ + std::vector pts; + if (sampleCount <= 0) + return pts; + + std::vector minv, maxv; + gmsh::model::getParametrizationBounds(1, gmshTag, minv, maxv); + if (minv.empty() || maxv.empty()) + return pts; + + double u1 = minv[0]; + double u2 = maxv[0]; + + for (int i = 1; i <= sampleCount; ++i) { + double t = double(i) / double(sampleCount + 1); + double u = (1.0 - t) * u1 + t * u2; + + std::vector coord; + gmsh::model::getValue(1, gmshTag, { u }, coord); + if (coord.size() >= 3) { + pts.push_back(coord[0]); + pts.push_back(coord[1]); + pts.push_back(coord[2]); + } + } + return pts; +} + +// ---- OCC 边特征 ---- +EdgeGeoFeature computeOCCEdgeFeature(int gid, const IncrementalMeshContext& ctx) +{ + TopoDS_Edge edge = ctx.getEdgeByGlobalId(gid); + + TopoDS_Vertex v1, v2; + TopExp::Vertices(edge, v1, v2); + + gp_Pnt p1 = BRep_Tool::Pnt(v1); + gp_Pnt p2 = BRep_Tool::Pnt(v2); + + GProp_GProps props; + BRepGProp::LinearProperties(edge, props); + + BRepAdaptor_Curve curve(edge); + double u1 = curve.FirstParameter(); + double u2 = curve.LastParameter(); + double um = 0.5 * (u1 + u2); + gp_Pnt pm = curve.Value(um); + + EdgeGeoFeature f {}; + f.x1 = p1.X(); + f.y1 = p1.Y(); + f.z1 = p1.Z(); + f.x2 = p2.X(); + f.y2 = p2.Y(); + f.z2 = p2.Z(); + + f.mx = pm.X(); + f.my = pm.Y(); + f.mz = pm.Z(); + + f.length = props.Mass(); + f.samples = sampleOCCEdgePoints(edge, 3); + return f; +} +double getShapeScale(const TopoDS_Shape& shape) +{ + Bnd_Box box; + BRepBndLib::Add(shape, box); + + double xmin, ymin, zmin, xmax, ymax, zmax; + box.Get(xmin, ymin, zmin, xmax, ymax, zmax); + + double dx = xmax - xmin; + double dy = ymax - ymin; + double dz = zmax - zmin; + double diag = std::sqrt(dx * dx + dy * dy + dz * dz); + + if (diag < 1e-12) + diag = 1.0; + return diag; +} + +// ---- GMSH 边特征 ---- +EdgeGeoFeature computeGmshEdgeFeature(int gmshTag) +{ + EdgeGeoFeature f {}; + + // 端点 + std::vector> vtxBnd; + gmsh::model::getBoundary({ { 1, gmshTag } }, vtxBnd, false, false, false); + + if (vtxBnd.size() >= 2) { + int v0 = std::abs(vtxBnd[0].second); + int v1 = std::abs(vtxBnd[1].second); + + std::vector p0, p1, param; + gmsh::model::getValue(0, v0, param, p0); + gmsh::model::getValue(0, v1, param, p1); + + if (p0.size() >= 3 && p1.size() >= 3) { + f.x1 = p0[0]; + f.y1 = p0[1]; + f.z1 = p0[2]; + + f.x2 = p1[0]; + f.y2 = p1[1]; + f.z2 = p1[2]; + } + } + + // 长度 + gmsh::model::occ::getMass(1, gmshTag, f.length); + + // 中点 + std::vector minv, maxv; + gmsh::model::getParametrizationBounds(1, gmshTag, minv, maxv); + if (!minv.empty() && !maxv.empty()) { + double um = 0.5 * (minv[0] + maxv[0]); + std::vector coord; + gmsh::model::getValue(1, gmshTag, { um }, coord); + if (coord.size() >= 3) { + f.mx = coord[0]; + f.my = coord[1]; + f.mz = coord[2]; + } + } + + f.samples = sampleGmshEdgePoints(gmshTag, 3); + return f; +} + +// ---- 匹配 ---- +std::map matchGmshToOCCEdges(const TopoDS_Face& face, + const IncrementalMeshContext& ctx) +{ + // OCC 当前 face 的边 + auto occIds = ctx.getFaceEdgeIds(face); + std::vector> occFeats; + for (int gid : occIds) { + occFeats.push_back({ gid, computeOCCEdgeFeature(gid, ctx) }); + } + + // 只取当前 Gmsh face 的边界边,而不是整个模型所有 edge + gmsh::vectorpair gmshFaceTags; + gmsh::model::getEntities(gmshFaceTags, 2); + if (gmshFaceTags.empty()) { + spdlog::warn(" No gmsh face found when matching edges"); + return {}; + } + + int gmshFaceTag = gmshFaceTags[0].second; + gmsh::vectorpair gmshEdgesRaw; + gmsh::model::getBoundary({ { 2, gmshFaceTag } }, gmshEdgesRaw, false, false, false); + + std::vector gmshEdges; + std::set uniqueEdges; + for (auto& [dim, tag] : gmshEdgesRaw) { + if (dim == 1 && !uniqueEdges.count(std::abs(tag))) { + uniqueEdges.insert(std::abs(tag)); + gmshEdges.push_back(std::abs(tag)); + } + } + + // 以当前 face 尺寸作为容差尺度 + double scale = getShapeScale(face); + double acceptTol = 5e-2; // 归一化后的阈值 + + std::map result; + std::set matched; + + for (int gmshTag : gmshEdges) { + auto gf = computeGmshEdgeFeature(gmshTag); + + double best = 1e30; + int bestId = -1; + + for (auto& [id, of] : occFeats) { + if (matched.count(id)) + continue; + + double d = gf.distanceTo(of, scale); + + spdlog::debug( + " edge candidate gmsh={} occ={} " + "endpoint={:.3e} length={:.3e} midpoint={:.3e} sample={:.3e} total={:.3e}", + gmshTag, id, + gf.endpointDistanceTo(of) / scale, + gf.lengthDistanceTo(of) / scale, + gf.midpointDistanceTo(of) / scale, + gf.sampleDistanceTo(of) / scale, + d); + + if (d < best) { + best = d; + bestId = id; + } + } + + if (bestId > 0 && best < acceptTol) { + result[gmshTag] = bestId; + matched.insert(bestId); + spdlog::info(" GMSH edge {} -> OCC edge {} (score={:.3e})", + gmshTag, bestId, best); + } else { + spdlog::warn(" GMSH edge {} UNMATCHED (best OCC={}, score={:.3e})", + gmshTag, bestId, best); + } + } + + spdlog::info(" Matched {}/{} edges", result.size(), gmshEdges.size()); + return result; +} + +// ---- 注入约束边 ---- +bool injectConstrainedEdge(int gmshTag, int occId, + const MeshedEdgeData& nodes, + std::size_t& nodeCounter, + std::size_t& elemCounter, + std::map& vtxNodeMap) +{ + if (nodes.empty()) + return false; + std::size_t nc = nodes.nodeCount(); + + std::vector> vtxBnd; + gmsh::model::getBoundary({ { 1, gmshTag } }, vtxBnd, false, false, false); + if (vtxBnd.size() < 2) + return false; + + int vtx0 = std::abs(vtxBnd[0].second); + int vtx1 = std::abs(vtxBnd[1].second); + + double gx0, gy0, gz0; + { + double a, b, c, d, e, f; + gmsh::model::getBoundingBox(0, vtx0, a, b, c, d, e, f); + gx0 = a; + gy0 = b; + gz0 = c; + } + + double distFwd = std::sqrt( + std::pow(gx0 - nodes.coords[0], 2) + std::pow(gy0 - nodes.coords[1], 2) + std::pow(gz0 - nodes.coords[2], 2)); + bool reversed = (distFwd > 1e-6); + + auto orderedCoords = nodes.coords; + auto orderedParams = nodes.paramCoords; + + if (reversed) { + std::vector rev(orderedCoords.size()); + for (std::size_t i = 0; i < nc; ++i) { + std::size_t ri = nc - 1 - i; + rev[i * 3] = orderedCoords[ri * 3]; + rev[i * 3 + 1] = orderedCoords[ri * 3 + 1]; + rev[i * 3 + 2] = orderedCoords[ri * 3 + 2]; + } + orderedCoords = rev; + orderedParams.clear(); + for (std::size_t i = 1; i + 1 < nc; ++i) { + try { + std::vector cc, cp; + gmsh::model::getClosestPoint(1, gmshTag, + { orderedCoords[i * 3], orderedCoords[i * 3 + 1], orderedCoords[i * 3 + 2] }, cc, cp); + if (!cp.empty()) + orderedParams.push_back(cp[0]); + } catch (...) { + orderedParams.push_back(double(i) / double(nc - 1)); + } + } + } + + // 起点 + std::size_t tagV0; + auto it0 = vtxNodeMap.find(vtx0); + if (it0 != vtxNodeMap.end()) { + tagV0 = it0->second; + } else { + tagV0 = nodeCounter++; + gmsh::model::mesh::addNodes(0, vtx0, { tagV0 }, + { orderedCoords[0], orderedCoords[1], orderedCoords[2] }); + vtxNodeMap[vtx0] = tagV0; + } + + // 终点 + std::size_t tagV1; + std::size_t last = nc - 1; + auto it1 = vtxNodeMap.find(vtx1); + if (it1 != vtxNodeMap.end()) { + tagV1 = it1->second; + } else { + tagV1 = nodeCounter++; + gmsh::model::mesh::addNodes(0, vtx1, { tagV1 }, + { orderedCoords[last * 3], orderedCoords[last * 3 + 1], orderedCoords[last * 3 + 2] }); + vtxNodeMap[vtx1] = tagV1; + } + + // 内部节点 + std::vector innerTags; + std::vector innerCoords, innerParams; + + for (std::size_t i = 1; i + 1 < nc; ++i) { + innerTags.push_back(nodeCounter++); + innerCoords.push_back(orderedCoords[i * 3]); + innerCoords.push_back(orderedCoords[i * 3 + 1]); + innerCoords.push_back(orderedCoords[i * 3 + 2]); + } + + if (orderedParams.size() == innerTags.size()) { + innerParams = orderedParams; + } else { + innerParams.clear(); + for (std::size_t i = 0; i < innerTags.size(); ++i) { + std::size_t ci = i + 1; + try { + std::vector cc, cp; + gmsh::model::getClosestPoint(1, gmshTag, + { orderedCoords[ci * 3], orderedCoords[ci * 3 + 1], orderedCoords[ci * 3 + 2] }, cc, cp); + if (!cp.empty()) + innerParams.push_back(cp[0]); + } catch (...) { + innerParams.push_back(double(i + 1) / double(nc - 1)); + } + } + } + + if (!innerTags.empty()) + gmsh::model::mesh::addNodes(1, gmshTag, innerTags, innerCoords, innerParams); + + // 线单元 + std::vector allN; + allN.push_back(tagV0); + for (auto t : innerTags) + allN.push_back(t); + allN.push_back(tagV1); + + std::vector eT, eN; + for (std::size_t i = 0; i + 1 < allN.size(); ++i) { + eT.push_back(elemCounter++); + eN.push_back(allN[i]); + eN.push_back(allN[i + 1]); + } + gmsh::model::mesh::addElementsByType(gmshTag, 1, eT, eN); + return true; +} + +// ---- 提取边节点 ---- +MeshedEdgeData extractEdgeNodes(int gmshTag) +{ + MeshedEdgeData en; + std::vector> vtxBnd; + gmsh::model::getBoundary({ { 1, gmshTag } }, vtxBnd, false, false, false); + if (vtxBnd.size() < 2) + return en; + + { + int vt = std::abs(vtxBnd[0].second); + std::vector nt; + std::vector co, pa; + gmsh::model::mesh::getNodes(nt, co, pa, 0, vt, false, false); + if (!nt.empty()) + en.coords.insert(en.coords.end(), co.begin(), co.begin() + 3); + } + + { + std::vector it; + std::vector ic, ip; + gmsh::model::mesh::getNodes(it, ic, ip, 1, gmshTag, false, true); + en.coords.insert(en.coords.end(), ic.begin(), ic.end()); + en.paramCoords.insert(en.paramCoords.end(), ip.begin(), ip.end()); + } + + { + int vt = std::abs(vtxBnd[1].second); + std::vector nt; + std::vector co, pa; + gmsh::model::mesh::getNodes(nt, co, pa, 0, vt, false, false); + if (!nt.empty()) + en.coords.insert(en.coords.end(), co.begin(), co.begin() + 3); + } + + return en; +} + +// ---- 提取面网格(局部索引)---- +SingleFaceMeshResult extractFaceMesh(int faceTag) +{ + SingleFaceMeshResult result; + result.face_vertices_offset.push_back(0); + + std::vector nodeTags; + std::vector coords, paramCoords; + gmsh::model::mesh::getNodes(nodeTags, coords, paramCoords, 2, faceTag, true, false); + + std::map tagToLocal; + for (size_t i = 0; i < nodeTags.size(); ++i) { + tagToLocal[nodeTags[i]] = result.vertices.size(); + result.vertices.push_back({ coords[3 * i], coords[3 * i + 1], coords[3 * i + 2] }); + } + + std::vector eTypes; + std::vector> eTags, eNodes; + gmsh::model::mesh::getElements(eTypes, eTags, eNodes, 2, faceTag); + + size_t cnt = 0; + for (size_t t = 0; t < eTypes.size(); ++t) { + if (eTypes[t] != 2 && eTypes[t] != 3) + continue; + int npe = (eTypes[t] == 2) ? 3 : 4; + for (size_t i = 0; i < eTags[t].size(); ++i) { + for (int j = 0; j < npe; ++j) + result.face_vertices.push_back(tagToLocal[eNodes[t][i * npe + j]]); + result.face_vertices_offset.push_back(result.face_vertices.size()); + cnt++; + } + } + result.success = (cnt > 0); + spdlog::info(" Extracted: {} nodes, {} elements", nodeTags.size(), cnt); + return result; +} + +// ---- 存储新边 ---- +void storeNewEdges(SplineData& sp, const std::map& g2o) +{ + int nNew = 0; + int nShared = 0; + for (auto& [gt, oid] : g2o) { + if (sp.meshedEdgeRefCounts.find(oid) == sp.meshedEdgeRefCounts.end()) { + auto ed = extractEdgeNodes(gt); + if (!ed.empty()) { + sp.meshedEdgesCache[oid] = std::move(ed); + sp.meshedEdgeRefCounts[oid] = 1; // 首次创建 + nNew++; + } + } else { + sp.meshedEdgeRefCounts[oid]++; // 被另一个面复用 + nShared++; + } + } + spdlog::info("GmshMesh: {} new edges stored, {} edges reused (total cached: {})", nNew, nShared, sp.meshedEdgeRefCounts.size()); +} + +void mergeMeshResult(MeshData& mesh_data, const SingleFaceMeshResult& result) +{ + if (!result.success) + return; + + if (mesh_data.face_vertices_offset_.empty()) + mesh_data.face_vertices_offset_.push_back(0); + + TempNodeLookup lookup(mesh_data.vertex_positions_, 1e-7); + + std::vector localToGlobal(result.vertices.size()); + for (size_t i = 0; i < result.vertices.size(); ++i) { + localToGlobal[i] = lookup.getOrInsert( + result.vertices[i][0], + result.vertices[i][1], + result.vertices[i][2]); + } + + for (size_t i = 0; i + 1 < result.face_vertices_offset.size(); ++i) { + size_t start = result.face_vertices_offset[i]; + size_t end = result.face_vertices_offset[i + 1]; + for (size_t j = start; j < end; ++j) { + mesh_data.face_vertices_.push_back( + static_cast(localToGlobal[result.face_vertices[j]])); + } + mesh_data.face_vertices_offset_.push_back( + static_cast(mesh_data.face_vertices_.size())); + } + + spdlog::info("GmshMesh: merged total {} nodes, {} cells", + mesh_data.vertex_positions_.size(), + mesh_data.face_vertices_offset_.size() - 1); +} + +} // anonymous namespace + +// 公开接口 +bool IncrementalMeshTools::initMeshing(const std::string& stepFile, + SplineData& spline) +{ + spline.clearMeshCache(); + spline.rootShape = std::make_unique(loadStep(stepFile)); + if (spline.rootShape->IsNull()) { + spdlog::error("Failed to load: {}", stepFile); + return false; + } + spline.meshContext = std::make_unique(*spline.rootShape); + spdlog::info("Loaded: {} faces, {} global edges", + spline.meshContext->faceCount(), + spline.meshContext->globalEdgeCount()); + return spline.meshContext->faceCount() > 0; +} + +SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( + MeshData& mesh_data, SplineData& sp, std::size_t faceIndex, double meshSize) +{ + SingleFaceMeshResult result; + + if (!sp.meshContext) { + spdlog::error("meshContext null, call initMeshing first"); + return result; + } + if (faceIndex >= sp.meshContext->faceCount()) { + spdlog::error("faceIndex {} out of range", faceIndex); + return result; + } + + spdlog::info("=== Meshing face {}/{} (size={:.6f}) ===", + faceIndex + 1, sp.meshContext->faceCount(), meshSize); + + TopoDS_Face face = sp.meshContext->getFaceByIndex(faceIndex); + if (face.IsNull()) { + spdlog::error("Face {} is null or invalid", faceIndex); + return result; + } + TopoDS_Compound compound = makeFaceCompound(face); + + gmsh::initialize(); + gmsh::option::setNumber("General.Terminal", 1); + gmsh::model::add("face_model"); + + gmsh::vectorpair outDimTags; + gmsh::model::occ::importShapesNativePointer( + static_cast(&compound), outDimTags); + gmsh::model::occ::synchronize(); + + std::vector> faceDimTags; + gmsh::model::getEntities(faceDimTags, 2); + if (faceDimTags.empty()) { + spdlog::error(" No face after import"); + gmsh::finalize(); + return result; + } + int faceTag = faceDimTags[0].second; + + auto gmshToOcc = matchGmshToOCCEdges(face, *sp.meshContext); + + std::size_t nodeCounter = 1, elemCounter = 1; + int shared = 0, free = 0; + std::map vtxNodeMap; + + for (auto& [gt, oid] : gmshToOcc) { + if (sp.meshedEdgeRefCounts.count(oid) > 0) { + injectConstrainedEdge(gt, oid, sp.meshedEdgesCache.at(oid), + nodeCounter, elemCounter, vtxNodeMap); + shared++; + } else { + free++; + } + } + spdlog::info(" {} shared, {} free", shared, free); + + gmsh::option::setNumber("Mesh.MeshSizeMin", meshSize * 0.5); + gmsh::option::setNumber("Mesh.MeshSizeMax", meshSize); + gmsh::option::setNumber("Mesh.MeshOnlyEmpty", 1); + gmsh::option::setNumber("Mesh.SaveAll", 1); + gmsh::option::setNumber("Mesh.Algorithm", 6); + + try { + gmsh::model::mesh::generate(2); + } catch (const std::exception& e) { + spdlog::error(" Mesh failed: {}", e.what()); + storeNewEdges(sp, gmshToOcc); + gmsh::finalize(); + return result; + } + + // 检查面单元 + { + std::vector ct; + std::vector> cta, cno; + gmsh::model::mesh::getElements(ct, cta, cno, 2, faceTag); + bool has = false; + for (size_t t = 0; t < ct.size(); ++t) + if ((ct[t] == 2 || ct[t] == 3) && !cta[t].empty()) { + has = true; + break; + } + if (!has) { + spdlog::warn(" No surface elements"); + storeNewEdges(sp, gmshToOcc); + gmsh::finalize(); + return result; + } + } + + result = extractFaceMesh(faceTag); + storeNewEdges(sp, gmshToOcc); + sp.meshedFacesCache[faceIndex] = result; // 缓存面结果 + + if (result.success) { + mergeMeshResult(mesh_data, result); + } + gmsh::finalize(); + return result; +} + +double IncrementalMeshTools::estimateMeshSize(const SplineData& sp) +{ + if (!sp.rootShape || sp.rootShape->IsNull()) + return 10.0; + GProp_GProps props; + BRepGProp::SurfaceProperties(*sp.rootShape, props); + double area = props.Mass(); + if (area > 0) { + double s = std::sqrt(area) / 10.0; + if (s < 0.01) + s = 0.01; + if (s > 100.0) + s = 100.0; + return s; + } + return 10.0; +} + +std::size_t IncrementalMeshTools::faceCount(const SplineData& sp) +{ + return sp.meshContext ? sp.meshContext->faceCount() : 0; +} + +std::size_t IncrementalMeshTools::meshedEdgeCount(const SplineData& sp) +{ + return sp.meshedEdgeRefCounts.size(); +} + +bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath) +{ + if (!res.success) + return false; + + std::ofstream ofs(filepath); + if (!ofs.is_open()) + return false; + + for (const auto& v : res.vertices) + ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; + + for (size_t i = 0; i + 1 < res.face_vertices_offset.size(); ++i) { + size_t start = res.face_vertices_offset[i]; + size_t end = res.face_vertices_offset[i + 1]; + ofs << "f"; + for (size_t j = start; j < end; ++j) + ofs << " " << (res.face_vertices[j] + 1); + ofs << "\n"; + } + return true; +} + +bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const std::filesystem::path& filepath) +{ + std::ofstream ofs(filepath); + if (!ofs.is_open()) + return false; + + ofs << "o GmshMergedMesh\n"; + ofs << "g 0\n"; + + for (const auto& v : res.vertex_positions_) + ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; + + for (size_t i = 0; i + 1 < res.face_vertices_offset_.size(); ++i) { + size_t start = res.face_vertices_offset_[i]; + size_t end = res.face_vertices_offset_[i + 1]; + ofs << "f"; + for (size_t j = start; j < end; ++j) + ofs << " " << (res.face_vertices_[j] + 1); + ofs << "\n"; + } + return true; +} + +bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, SplineData& spline, std::size_t faceIndex) +{ + // 检查是否存在该面的缓存 + auto it = spline.meshedFacesCache.find(faceIndex); + if (it == spline.meshedFacesCache.end()) { + spdlog::warn("Face {} is not meshed or not cached.", faceIndex); + return false; + } + + // 释放面的边界边(更新引用计数并在归零时移除) + if (spline.meshContext) { + TopoDS_Face face = spline.meshContext->getFaceByIndex(faceIndex); + auto occEdges = spline.meshContext->getFaceEdgeIds(face); + for (int globalEdgeId : occEdges) { + auto refIt = spline.meshedEdgeRefCounts.find(globalEdgeId); + if (refIt != spline.meshedEdgeRefCounts.end()) { + refIt->second--; + if (refIt->second <= 0) { + spline.meshedEdgeRefCounts.erase(refIt); + spline.meshedEdgesCache.erase(globalEdgeId); + spdlog::info("Edge {} cache cleaned up.", globalEdgeId); + } + } + } + } + + spline.meshedFacesCache.erase(it); + + // 重构meshdata 暂时重建面索引 + mesh_data.face_vertices_.clear(); + mesh_data.face_vertices_offset_.clear(); + mesh_data.face_vertices_offset_.push_back(0); + + TempNodeLookup lookup(mesh_data.vertex_positions_, 1e-7); + + // 遍历目前还保留的所有有效面,按序装入MeshData + for (const auto& [fIdx, faceMeshResult] : spline.meshedFacesCache) { + if (!faceMeshResult.success) + continue; + + std::vector localToGlobal(faceMeshResult.vertices.size()); + for (size_t i = 0; i < faceMeshResult.vertices.size(); ++i) { + localToGlobal[i] = lookup.getOrInsert( + faceMeshResult.vertices[i][0], + faceMeshResult.vertices[i][1], + faceMeshResult.vertices[i][2]); + } + + for (size_t i = 0; i + 1 < faceMeshResult.face_vertices_offset.size(); ++i) { + size_t start = faceMeshResult.face_vertices_offset[i]; + size_t end = faceMeshResult.face_vertices_offset[i + 1]; + for (size_t j = start; j < end; ++j) { + mesh_data.face_vertices_.push_back( + static_cast(localToGlobal[faceMeshResult.face_vertices[j]])); + } + mesh_data.face_vertices_offset_.push_back( + static_cast(mesh_data.face_vertices_.size())); + } + } + + spdlog::info("Deleted mesh for face {}, rebuilt topology. Remaining cells: {}", + faceIndex, mesh_data.face_vertices_offset_.size() - 1); + return true; +} + +SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( + MeshData& mesh_data, SplineData& sp, std::size_t faceIndex, double meshSize) +{ + if (sp.meshedFacesCache.find(faceIndex) != sp.meshedFacesCache.end()) { + spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); + deleteFaceMesh(mesh_data, sp, faceIndex); + } + return meshSingleFace(mesh_data, sp, faceIndex, meshSize); +} diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h new file mode 100644 index 0000000..c3c924b --- /dev/null +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include "SplineData.h" + +struct MeshData; + +namespace IncrementalMeshTools { + +bool initMeshing(const std::string& stepFile, SplineData& spline); + +SingleFaceMeshResult meshSingleFace( + MeshData& mesh_data, + SplineData& spline, + std::size_t faceIndex, + double meshSize); + +SingleFaceMeshResult remeshSingleFace( + MeshData& mesh_data, + SplineData& spline, + std::size_t faceIndex, + double meshSize); + +bool deleteFaceMesh(MeshData& mesh_data, SplineData& spline, std::size_t faceIndex); + +double estimateMeshSize(const SplineData& spline); + +std::size_t faceCount(const SplineData& spline); + +std::size_t meshedEdgeCount(const SplineData& spline); + +bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); + +bool writeMeshObj(const MeshData& res, const std::filesystem::path& filepath); + +} // namespace IncrementalMeshTools \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt new file mode 100644 index 0000000..55ab140 --- /dev/null +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -0,0 +1,33 @@ +precess_add_test(GmshPluginTest GmshPluginTest.cpp) +precess_test_link_libraries(GmshPluginTest + Data + Systems + Core + TKPrim + TKBRep + TKTopAlgo + TKernel + TKG3d + GmshPluginlib +) + +# GmshVtkExample +add_executable(GmshVtkExample "GmshVtkExample.cpp") +target_link_libraries(GmshVtkExample PRIVATE + gmsh::shared + vtkPart TestDeps + VTK::InteractionStyle VTK::CommonColor VTK::RenderingCore VTK::RenderingUI VTK::RenderingOpenGL2 VTK::FiltersGeometry + TKBRep # BRep_Builder, BRepTools + TKernel # Standard_* 基础类型 + TKG3d # gp_* 准则基础 + TKDESTEP # STEPControl_Reader + TKXSBase # STEP/IGES 转换基础 + TKTopAlgo # TopExp_Explorer + GmshPluginlib +) +add_custom_command(TARGET GmshVtkExample POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMENT "Copying gmsh.dll to GmshVtkExample output directory" +) \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp new file mode 100644 index 0000000..e707c55 --- /dev/null +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -0,0 +1,78 @@ +#include + +#include "GmshMeshHandler.h" +#include "IncrementalMeshContext.h" +#include "IncrementalMeshTools.h" + +#include "ArgObject.h" +#include "ModelData.h" +#include "ModelIOSystemBase.h" +#include "ModelOperator.h" +#include "SplineData.h" + +#include +#include + +#include + +namespace { + +// 模拟的 IO 系统,避免在测试中涉及真实的界面交互或不可控流 +class MockIOSystem : public systems::io::ModelIOSystemBase { +public: + void read(const std::filesystem::path& path, const std::string& file_type, const std::vector& args) override + { + spdlog::info("[MockIO] read file: {}", path.string()); + } + void write(Index model, const std::filesystem::path& path, const std::string& file_type, const std::vector& args) override + { + spdlog::info("[MockIO] write to: {}", path.string()); + } +}; + +} // namespace + +TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") +{ + spdlog::set_level(spdlog::level::info); + + // 1. 使用 OCC 构造一个 10x10x10 的立方体形状 + BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0); + boxMaker.Build(); + REQUIRE(boxMaker.IsDone() == true); + TopoDS_Shape cubeShape = boxMaker.Shape(); + + // 2. 初始化 SplineData 及持有它的 ModelData + auto splineData = std::make_unique(); + splineData->rootShape = std::make_unique(cubeShape); + + ModelData modelData(std::move(splineData)); + ModelOperator op(1, modelData); + + // 3. 构建模拟的 IO 系统和上下文环境 + MockIOSystem mockIo; + systems::algo::HandlerContext context { mockIo, op }; + + // 4. 准备入参: + // Arg 0: 面索引 "0" + // Arg 1: 网格尺寸 "2.0" + std::vector args; + args.push_back(core::ArgObject::create("0")); + args.push_back(core::ArgObject::create("2.0")); + + // 5. 调用执行插件方法 + systems::algo::GmshMeshHandler handler; + std::any result = handler.execute(context, args); + + // 6. 后置断言:验证执行后的内部状态变化 + SplineData* sp = op.data().asSplineData(); + REQUIRE(sp != nullptr); + REQUIRE(sp->meshContext != nullptr); + + // 立方体应当被提取到了 6 个面 + REQUIRE(sp->meshContext->faceCount() == 6); + + // 由于面 0 已经成功完成了网格划分,其关联的 4 条自由边界应该被缓存起来了 + REQUIRE(sp->meshedEdgeRefCounts.size() == 4); + REQUIRE(sp->meshedEdgesCache.size() == 4); +} \ No newline at end of file diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp new file mode 100644 index 0000000..eb36596 --- /dev/null +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -0,0 +1,215 @@ +#include + +#include +#include +#include +#include +#include "IncrementalMeshTools.h" +#include "IncrementalMeshTools.cpp" + +#include "SplineData.h" +#include "MeshData.h" +#include "MeshActor.h" +#include "ModelData.h" +#include "MakeMeshDataVtk.h" +#include +#include +#include +#include +#include +#include + +// ================================================================ +// 保存 +// ================================================================ +static void saveMesh(const MeshData& md, const std::string& filename) +{ + if (md.vertex_positions_.empty()) { + spdlog::warn("No mesh data to save."); + return; + } + try { + gmsh::initialize(); + gmsh::model::add("merged"); + int tag = gmsh::model::addDiscreteEntity(2); + + std::vector nt(md.vertex_positions_.size()); + std::vector nc(md.vertex_positions_.size() * 3); + for (size_t i = 0; i < md.vertex_positions_.size(); ++i) { + nt[i] = i + 1; + nc[i * 3] = md.vertex_positions_[i][0]; + nc[i * 3 + 1] = md.vertex_positions_[i][1]; + nc[i * 3 + 2] = md.vertex_positions_[i][2]; + } + gmsh::model::mesh::addNodes(2, tag, nt, nc); + + std::vector tt, tn, qt, qn; + std::size_t ec = 1; + for (size_t i = 0; i + 1 < md.face_vertices_offset_.size(); ++i) { + size_t s = md.face_vertices_offset_[i]; + size_t e = md.face_vertices_offset_[i + 1]; + size_t c = e - s; + if (c == 3) { + tt.push_back(ec++); + for (size_t j = s; j < e; ++j) + tn.push_back(md.face_vertices_[j] + 1); + } else if (c == 4) { + qt.push_back(ec++); + for (size_t j = s; j < e; ++j) + qn.push_back(md.face_vertices_[j] + 1); + } + } + if (!tt.empty()) + gmsh::model::mesh::addElementsByType(tag, 2, tt, tn); + if (!qt.empty()) + gmsh::model::mesh::addElementsByType(tag, 3, qt, qn); + + gmsh::write(filename); + spdlog::info("Saved: {}", std::filesystem::absolute(filename).string()); + gmsh::finalize(); + } catch (const std::exception& e) { + spdlog::error("Save failed: {}", e.what()); + if (gmsh::isInitialized()) + gmsh::finalize(); + } +} + +// ================================================================ +// AppContext + 回调 +// ================================================================ +struct AppContext { + SplineData* spline; + MeshActor* actor; + vtkRenderWindow* window; + + // 累积状态 + MeshData meshData; + std::size_t currentIndex = 0; + double meshSize = 10.0; + + void init(double estimatedSize) + { + meshData.clear(); + currentIndex = 0; + meshSize = estimatedSize; + + meshData.face_vertices_offset_.push_back(0); + if (meshData.solid_vertices_offset_.empty()) + meshData.solid_vertices_offset_.push_back(0); + if (meshData.solid_faces_vertices_offset_.empty()) + meshData.solid_faces_vertices_offset_.push_back(0); + if (meshData.solid_faces_offset_.empty()) + meshData.solid_faces_offset_.push_back(0); + } +}; + +static void KeyPressCallback(vtkObject* caller, unsigned long, + void* clientData, void*) +{ + auto* ctx = static_cast(clientData); + auto* interactor = static_cast(caller); + std::string key = interactor->GetKeySym(); + + std::size_t total = IncrementalMeshTools::faceCount(*ctx->spline); + + if (key == "space") { + if (ctx->currentIndex >= total) { + spdlog::info("All faces meshed!"); + return; + } + spdlog::info(">>> Face {}/{} (size={:.4f})", + ctx->currentIndex + 1, total, ctx->meshSize); + + auto res = IncrementalMeshTools::meshSingleFace( + ctx->meshData, *ctx->spline, ctx->currentIndex, ctx->meshSize); + ctx->currentIndex++; + + if (res.success) { + ctx->actor->loadModelData(MakeMeshDataVtk(ctx->meshData)); + ctx->window->Render(); + } + + if (ctx->meshSize < 50.0) + ctx->meshSize *= 1.5; + spdlog::info(" nodes={}, edges={}, next_size={:.4f}", + ctx->meshData.vertex_positions_.size(), + IncrementalMeshTools::meshedEdgeCount(*ctx->spline), + ctx->meshSize); + } else if (key == "s" || key == "S") + saveMesh(ctx->meshData, "final_mesh.msh"); + else if (key == "r" || key == "R") { + ctx->meshSize = IncrementalMeshTools::estimateMeshSize(*ctx->spline); + spdlog::info("Clean vertices,Reset size: {:.4f}", ctx->meshSize); + } else if (key == "plus" || key == "equal") { + ctx->meshSize *= 2.0; + spdlog::info("Size: {:.4f}", ctx->meshSize); + } else if (key == "minus") { + ctx->meshSize *= 0.5; + spdlog::info("Size: {:.4f}", ctx->meshSize); + } else if (key == "d" || key == "D") { + if (ctx->currentIndex == 0) { + spdlog::warn("没有可以删除的网格面了!"); + } else { + ctx->currentIndex--; + spdlog::info("Delete mesh for face: {}", ctx->currentIndex); + + if (ctx->currentIndex == 0) { + ctx->init(ctx->meshSize); + } else { + IncrementalMeshTools::deleteFaceMesh(ctx->meshData, *ctx->spline, ctx->currentIndex); + } + + ctx->actor->loadModelData(MakeMeshDataVtk(ctx->meshData)); + ctx->window->Render(); + } + } else if (key == "h" || key == "H") { + spdlog::info("SPACE=mesh, S=save, R=reset, +/-=size, D=delete, H=help"); + } +} + +// ================================================================ +// Main +// ================================================================ +int main(int argc, char* argv[]) +{ + spdlog::set_level(spdlog::level::info); + + SplineData spline; + + std::string path = (argc > 1) + ? argv[1] + : "E:/VSProject/_models/models/airplane.stp"; + + spdlog::info("Loading: {}", path); + + if (!IncrementalMeshTools::initMeshing(path, spline)) { + spdlog::error("Cannot import: {}", path); + return 1; + } + + auto renderer = vtkSmartPointer::New(); + renderer->SetBackground(0.1, 0.15, 0.2); + + auto window = vtkSmartPointer::New(); + window->AddRenderer(renderer); + window->SetSize(1024, 768); + window->SetWindowName("GmshMesh Test"); + + auto interactor = vtkSmartPointer::New(); + interactor->SetRenderWindow(window); + + auto actor = std::make_shared( + renderer, true, true, ModelRenderMode::Face); + + AppContext ctx{ &spline, actor.get(), window }; + ctx.init(IncrementalMeshTools::estimateMeshSize(spline)); + + auto cb = vtkSmartPointer::New(); + cb->SetCallback(KeyPressCallback); + cb->SetClientData(&ctx); + interactor->AddObserver(vtkCommand::KeyPressEvent, cb); + + window->Render(); + interactor->Start(); + return 0; +} \ No newline at end of file -- Gitee From 78a292f9816ddb32ec8331f678419d6888a4cc8e Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sat, 23 May 2026 20:01:07 +0800 Subject: [PATCH 02/21] =?UTF-8?q?feat(GmshPlugin):=20example=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/data/CMakeLists.txt | 21 +- model/data/GeometryData.cpp | 13 + model/data/GeometryData.h | 36 +++ model/data/IncrementalMeshContext.cpp | 65 ++++ model/data/IncrementalMeshContext.h | 34 ++ model/data/ModelLayer.h | 4 +- plugins/algo/CMakeLists.txt | 3 +- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 119 +++---- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 177 ++++++----- .../algo/GmshPlugin/IncrementalMeshTools.h | 23 +- plugins/algo/GmshPlugin/test/CMakeLists.txt | 28 +- .../algo/GmshPlugin/test/GmshPluginTest.cpp | 70 +++-- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 295 ++++++++++-------- 13 files changed, 568 insertions(+), 320 deletions(-) create mode 100644 model/data/IncrementalMeshContext.cpp create mode 100644 model/data/IncrementalMeshContext.h diff --git a/model/data/CMakeLists.txt b/model/data/CMakeLists.txt index bb0e2e8..6faf7b9 100644 --- a/model/data/CMakeLists.txt +++ b/model/data/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(Data STATIC ModelData.cpp MeshData.cpp GeometryData.cpp + IncrementalMeshContext.cpp ModelLayer.cpp ModelOperator.cpp GeometryRegistry.cpp @@ -17,6 +18,24 @@ target_link_libraries(Data PUBLIC Core spdlog::spdlog $<$:ws2_32> # spdlog日志库 ) if (OpenCASCADE_FOUND) + if (DEFINED OpenCASCADE_INCLUDE_DIR) + target_include_directories(Data PUBLIC ${OpenCASCADE_INCLUDE_DIR}) + elseif (DEFINED OpenCASCADE_INCLUDE_DIRS) + target_include_directories(Data PUBLIC ${OpenCASCADE_INCLUDE_DIRS}) + else() + message(FATAL_ERROR "OpenCASCADE found but include dir variable not set (OpenCASCADE_INCLUDE_DIR/S not found)") + endif() + + target_link_libraries(Data PRIVATE + TKernel + TKMath + TKG2d + TKG3d + TKGeomBase + TKBRep + TKTopAlgo + ) + target_link_libraries(Data PUBLIC $) target_link_libraries(Data PRIVATE TKernel TKTopAlgo) else() @@ -27,4 +46,4 @@ else() ) endif() -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/model/data/GeometryData.cpp b/model/data/GeometryData.cpp index 459e59b..166db44 100644 --- a/model/data/GeometryData.cpp +++ b/model/data/GeometryData.cpp @@ -1,7 +1,20 @@ #include "GeometryData.h" #include "GeometryDataVtk.h" +#include "IncrementalMeshContext.h" #include +GmshIncrementalMeshState::GmshIncrementalMeshState() = default; +GmshIncrementalMeshState::~GmshIncrementalMeshState() = default; + +void GmshIncrementalMeshState::clear() +{ + meshContext.reset(); + meshedEdgesCache.clear(); + meshedFacesCache.clear(); + meshedEdgeRefCounts.clear(); + local_to_global_point_ids.clear(); +} + GeometryData::GeometryData() = default; GeometryData::~GeometryData() = default; diff --git a/model/data/GeometryData.h b/model/data/GeometryData.h index bf503c9..87a4113 100644 --- a/model/data/GeometryData.h +++ b/model/data/GeometryData.h @@ -1,10 +1,46 @@ #pragma once #include #include +#include +#include +#include #include "GeometrySubshapeIndex.h" class TopoDS_Shape; struct GeometryDataVtk; +class IncrementalMeshContext; + +// 保存一条已生成网格的 CAD 边,供相邻面复用边界节点。 +struct MeshedEdgeData { + std::vector coords; + std::vector paramCoords; + + std::size_t nodeCount() const { return coords.size() / 3; } + bool empty() const { return coords.empty(); } +}; + +// 保存单个 CAD 面的网格结果,面删除和重划分时用它重建整体 MeshData。 +struct SingleFaceMeshResult { + std::vector> vertices; + std::vector face_vertices; + std::vector face_vertices_offset; + bool success { false }; +}; + +// GeometryData 上的 Gmsh 增量网格状态,生命周期跟随当前 CAD component。 +struct GmshIncrementalMeshState { + GmshIncrementalMeshState(); + ~GmshIncrementalMeshState(); + + std::unique_ptr meshContext; + std::map meshedEdgesCache; + std::map meshedFacesCache; + std::map meshedEdgeRefCounts; + std::vector local_to_global_point_ids; + + // 清空当前 CAD component 的增量网格缓存,不影响 GeometryData::rootShape。 + void clear(); +}; // 以后需要控制点 / 曲率等,可继续添加字段 struct GeometryData { diff --git a/model/data/IncrementalMeshContext.cpp b/model/data/IncrementalMeshContext.cpp new file mode 100644 index 0000000..dda2688 --- /dev/null +++ b/model/data/IncrementalMeshContext.cpp @@ -0,0 +1,65 @@ +#include "IncrementalMeshContext.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct IncrementalMeshContext::Impl { + TopTools_IndexedMapOfShape globalEdgeMap; + TopTools_IndexedMapOfShape globalFaceMap; +}; + +IncrementalMeshContext::IncrementalMeshContext(const TopoDS_Shape& shape) + : impl_(std::make_unique()) +{ + TopExp::MapShapes(shape, TopAbs_EDGE, impl_->globalEdgeMap); + TopExp::MapShapes(shape, TopAbs_FACE, impl_->globalFaceMap); +} + +IncrementalMeshContext::~IncrementalMeshContext() = default; + +int IncrementalMeshContext::globalEdgeCount() const +{ + return impl_->globalEdgeMap.Extent(); +} + +std::vector IncrementalMeshContext::getFaceEdgeIds(const TopoDS_Face& face) const +{ + std::vector ids; + std::set seen; + for (TopExp_Explorer ex(face, TopAbs_EDGE); ex.More(); ex.Next()) { + int gid = impl_->globalEdgeMap.FindIndex(ex.Current()); + if (gid > 0 && seen.insert(gid).second) + ids.push_back(gid); + } + return ids; +} + +TopoDS_Edge IncrementalMeshContext::getEdgeByGlobalId(int globalId) const +{ + if (globalId < 1 || globalId > impl_->globalEdgeMap.Extent()) + throw std::out_of_range("invalid edge ID " + std::to_string(globalId)); + return TopoDS::Edge(impl_->globalEdgeMap(globalId)); +} + +std::size_t IncrementalMeshContext::faceCount() const +{ + return static_cast(impl_->globalFaceMap.Extent()); +} + +TopoDS_Face IncrementalMeshContext::getFaceByIndex(std::size_t index) const +{ + int occIndex = static_cast(index) + 1; + if (occIndex < 1 || occIndex > impl_->globalFaceMap.Extent()) + throw std::out_of_range("face index " + std::to_string(index) + " out of range"); + return TopoDS::Face(impl_->globalFaceMap(occIndex)); +} diff --git a/model/data/IncrementalMeshContext.h b/model/data/IncrementalMeshContext.h new file mode 100644 index 0000000..d725a7d --- /dev/null +++ b/model/data/IncrementalMeshContext.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +class TopoDS_Shape; +class TopoDS_Face; +class TopoDS_Edge; + +// 为增量网格划分缓存 OCC 拓扑索引,避免每次划分单个面时重复遍历整棵 Shape。 +class IncrementalMeshContext { +public: + explicit IncrementalMeshContext(const TopoDS_Shape& shape); + ~IncrementalMeshContext(); + + IncrementalMeshContext(const IncrementalMeshContext&) = delete; + IncrementalMeshContext& operator=(const IncrementalMeshContext&) = delete; + + // 返回当前 Shape 中注册到全局 OCC 边索引的边数量。 + int globalEdgeCount() const; + // 返回指定面包含的全局 OCC 边 ID,供 Gmsh 边界复用逻辑使用。 + std::vector getFaceEdgeIds(const TopoDS_Face& face) const; + // 根据全局 OCC 边 ID 取回原始 TopoDS_Edge。 + TopoDS_Edge getEdgeByGlobalId(int globalId) const; + + // 返回当前 Shape 中可独立划分的面数量。 + std::size_t faceCount() const; + // 根据 0 起始面索引取回 TopoDS_Face。 + TopoDS_Face getFaceByIndex(std::size_t index) const; + +private: + struct Impl; + std::unique_ptr impl_; +}; diff --git a/model/data/ModelLayer.h b/model/data/ModelLayer.h index b89ce9b..e134208 100644 --- a/model/data/ModelLayer.h +++ b/model/data/ModelLayer.h @@ -80,6 +80,9 @@ public: MeshIDMap& edgeIdMap(); const MeshIDMap& edgeIdMap() const; + // 将运行期新生成的点追加到全局点池,返回这批点的第一个全局点 ID。 + Index appendGlobalPoints(const std::vector>& pts); + private: Index allocateComponentId() noexcept; @@ -91,7 +94,6 @@ private: Index max_index_{ -1 }; //!< 最大索引值,用于唯一标识模型 Index next_component_id_ { 0 }; //!< component_id 全局发号器(只增不减) - Index appendGlobalPoints(const std::vector>& pts); std::vector> global_points_; MeshIDMap edge_id_map_; // 先只维护 edge 的 global->local diff --git a/plugins/algo/CMakeLists.txt b/plugins/algo/CMakeLists.txt index 1da5255..55c4c27 100644 --- a/plugins/algo/CMakeLists.txt +++ b/plugins/algo/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(CmdExecutePlugin) -add_subdirectory(TetGenPlugin) \ No newline at end of file +add_subdirectory(TetGenPlugin) +add_subdirectory(GmshPlugin) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 5dbab11..e347ddd 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -1,18 +1,20 @@ #include "GmshMeshHandler.h" -#include "ModelOperatorBase.h" + #include "ArgObject.h" +#include "ComponentData.h" +#include "ComponentOperator.h" +#include "GeometryData.h" #include "IncrementalMeshContext.h" #include "IncrementalMeshTools.h" -#include "ModelData.h" -#include "ModelIOSystemBase.h" -#include "SplineData.h" -#include "ModelOperator.h" #include "MeshData.h" +#include "ModelIOSystemBase.h" +#include "ModelLayer.h" +#include "TempFile.h" + #include #include #include -#include using core::ArgType; @@ -20,10 +22,9 @@ std::any systems::algo::GmshMeshHandler::execute( HandlerContext& context, const std::vector& args) { - // 解析参数:面索引 + 网格尺寸 + 操作类型 int faceIndex = -1; double meshSize = 0.0; - int operationMode = 1; // 默认 1:网格划分 + int operationMode = 1; if (args.size() >= 1) { const std::string* idxStr = args[0].get(); @@ -35,6 +36,7 @@ std::any systems::algo::GmshMeshHandler::execute( } } } + if (args.size() >= 2) { const std::string* sizeStr = args[1].get(); if (sizeStr && !sizeStr->empty()) { @@ -45,6 +47,7 @@ std::any systems::algo::GmshMeshHandler::execute( } } } + if (args.size() >= 3) { const std::string* opStr = args[2].get(); if (opStr && !opStr->empty()) { @@ -61,48 +64,33 @@ std::any systems::algo::GmshMeshHandler::execute( return {}; } - // 获取 SplineData - if (context.cur_model.getType() != ModelData::Type::Spline) { - spdlog::error("GmshMesh: need Spline model"); - return {}; - } - ModelOperator* op = dynamic_cast(&context.cur_model); - if (!op) { - spdlog::error("GmshMesh: cant get ModelOperator"); + ComponentData& comp = context.cur_component.component(); + GeometryData* geometry = comp.geometry.get(); + if (!geometry || !geometry->rootShape) { + spdlog::error("GmshMesh: current component {} has no geometry", + context.cur_component.componentId()); return {}; } - SplineData* sp = op->data().asSplineData(); - if (!sp || !sp->rootShape) { - spdlog::error("GmshMesh: SplineData is illegal"); - return {}; - } - - // 获取 MeshData - MeshData* mesh_data = op->data().getMeshData(); - if (!mesh_data) { + // 获取或创建当前 component 的 MeshData,Gmsh 生成结果直接写回该 component。 + MeshData* meshData = comp.mesh.get(); + if (!meshData) { spdlog::info("GmshMesh: mesh_data is null, create a new one"); - auto new_mesh = std::make_unique(); - new_mesh->init(); - op->data().setMeshData(std::move(new_mesh)); - mesh_data = op->data().getMeshData(); - if (!mesh_data) { - spdlog::error("GmshMesh: failed to create mesh_data"); - return {}; - } + comp.mesh = std::make_unique(); + comp.mesh->init(); + meshData = comp.mesh.get(); } - - // 首次调用:初始化拓扑索引 - if (!sp->meshContext) { + + // 首次执行时建立 OCC 面/边索引,后续单面划分复用该上下文。 + if (!geometry->gmsh_mesh_state.meshContext) { spdlog::info("GmshMesh: init occId..."); - sp->meshContext = std::make_unique(*sp->rootShape); + geometry->gmsh_mesh_state.meshContext = std::make_unique(*geometry->rootShape); spdlog::info("GmshMesh: {} face, {} global edge", - sp->meshContext->faceCount(), - sp->meshContext->globalEdgeCount()); + geometry->gmsh_mesh_state.meshContext->faceCount(), + geometry->gmsh_mesh_state.meshContext->globalEdgeCount()); } - // 检查面索引范围 - std::size_t totalFaces = sp->meshContext->faceCount(); + std::size_t totalFaces = geometry->gmsh_mesh_state.meshContext->faceCount(); if (static_cast(faceIndex) >= totalFaces) { spdlog::error("GmshMesh: face id {} out of range (total {} face)", faceIndex, totalFaces); @@ -110,27 +98,23 @@ std::any systems::algo::GmshMeshHandler::execute( } if (meshSize <= 0.0) - meshSize = IncrementalMeshTools::estimateMeshSize(*sp); + meshSize = IncrementalMeshTools::estimateMeshSize(*geometry); SingleFaceMeshResult result; + ModelLayer& modelLayer = context.cur_component.manager(); if (operationMode == 2) { - // 删除网格 spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); - if (IncrementalMeshTools::deleteFaceMesh(*mesh_data, *sp, static_cast(faceIndex))) - spdlog::info("GmshMesh: delete face {} sucess", faceIndex); - } - else if (operationMode == 3) { - // 重划分网格 + if (IncrementalMeshTools::deleteFaceMesh(*meshData, *geometry, modelLayer, static_cast(faceIndex))) + spdlog::info("GmshMesh: delete face {} success", faceIndex); + } else if (operationMode == 3) { spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::remeshSingleFace( - *mesh_data, *sp, static_cast(faceIndex), meshSize); - } - else { - // 划分网格 (默认) + *meshData, *geometry, modelLayer, static_cast(faceIndex), meshSize); + } else { spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::meshSingleFace( - *mesh_data, *sp, static_cast(faceIndex), meshSize); + *meshData, *geometry, modelLayer, static_cast(faceIndex), meshSize); } if (operationMode != 2) { @@ -139,29 +123,28 @@ std::any systems::algo::GmshMeshHandler::execute( return {}; } - spdlog::info("GmshMesh:face {} finish , {} nodes, {} cells , cached {} edge", + spdlog::info("GmshMesh: face {} finish, {} nodes, {} cells, cached {} edge", faceIndex, result.vertices.size(), result.face_vertices_offset.size() - 1, - sp->meshedEdgeRefCounts.size()); + geometry->gmsh_mesh_state.meshedEdgeRefCounts.size()); - // 单面输出为obj - std::string face_out = core::TempFile::instance().path().string() +"_single_face_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeSingleFaceObj(result, face_out)) { - spdlog::error("GmshMesh: cant save single face "); + std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; + if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { + spdlog::error("GmshMesh: cant save single face"); return {}; } - context.io_system.read(face_out, "Wavefront .obj file", {}); + context.io_system.read(faceOut, "Wavefront .obj file", {}); } - // 将总的mesh_data写出为obj - std::string mesh_out = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeMeshObj(*mesh_data, mesh_out)) { - spdlog::error("GmshMesh: cant save meshdata "); + std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; + if (!IncrementalMeshTools::writeMeshObj(*meshData, *geometry, meshOut)) { + spdlog::error("GmshMesh: cant save meshdata"); return {}; } - context.io_system.read(mesh_out, "Wavefront .obj file", {}); - + context.io_system.read(meshOut, "Wavefront .obj file", {}); + context.cur_component.notifyChanged(); + return {}; } @@ -170,6 +153,6 @@ std::vector systems::algo::GmshMeshHandler::args_type() const return { ArgType { ArgTypeEnum::Text, "面索引(0开始)", "" }, ArgType { ArgTypeEnum::Text, "网格尺寸(留空自动)", "" }, - ArgType { ArgTypeEnum::Text, "1:mesh 2:delete 3:remesh", "" } + ArgType { ArgTypeEnum::Text, "1: mesh 2: delete 3: remesh", "" } }; -} \ No newline at end of file +} diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index c231fa2..2f369df 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -2,6 +2,7 @@ #include "IncrementalMeshContext.h" #include "MeshData.h" +#include "ModelLayer.h" #include #include @@ -37,28 +38,43 @@ namespace { class TempNodeLookup { public: explicit TempNodeLookup( - std::vector>& vertices, + MeshData& mesh_data, + GeometryData& geometry, + ModelLayer& model_layer, double tolerance = 1e-7) - : _vertices(vertices) + : _mesh_data(mesh_data) + , _geometry(geometry) + , _model_layer(model_layer) , _tolerance(tolerance) { - for (size_t i = 0; i < _vertices.size(); ++i) { - auto qc = _quantize(_vertices[i][0], _vertices[i][1], _vertices[i][2]); - _map[qc] = i; + const auto& vertices = _mesh_data.vertex_positions_; + const auto& global_ids = _geometry.gmsh_mesh_state.local_to_global_point_ids; + for (size_t i = 0; i < vertices.size(); ++i) { + auto qc = _quantize(vertices[i][0], vertices[i][1], vertices[i][2]); + if (i < global_ids.size()) { + _map[qc] = global_ids[i]; + } } } - size_t getOrInsert(double x, double y, double z) + Index getOrInsert(double x, double y, double z) { auto qc = _quantize(x, y, z); auto it = _map.find(qc); if (it != _map.end()) return it->second; - size_t idx = _vertices.size(); - _vertices.push_back({ x, y, z }); - _map[qc] = idx; - return idx; + std::array point { x, y, z }; + Index global_id = _model_layer.appendGlobalPoints({ point }); + if (_mesh_data.global_point_base_ < 0) { + _mesh_data.global_point_base_ = global_id; + } + _mesh_data.vertex_positions_.push_back(point); + _mesh_data.vertex_count_ = static_cast(_mesh_data.vertex_positions_.size()); + _mesh_data.point_ids_are_global_ = true; + _geometry.gmsh_mesh_state.local_to_global_point_ids.push_back(global_id); + _map[qc] = global_id; + return global_id; } private: @@ -90,9 +106,11 @@ private: }; } - std::vector>& _vertices; + MeshData& _mesh_data; + GeometryData& _geometry; + ModelLayer& _model_layer; double _tolerance; - std::unordered_map _map; + std::unordered_map _map; }; // ---- 加载 STEP ---- @@ -645,27 +663,28 @@ SingleFaceMeshResult extractFaceMesh(int faceTag) } // ---- 存储新边 ---- -void storeNewEdges(SplineData& sp, const std::map& g2o) +void storeNewEdges(GeometryData& geometry, const std::map& g2o) { + auto& state = geometry.gmsh_mesh_state; int nNew = 0; int nShared = 0; for (auto& [gt, oid] : g2o) { - if (sp.meshedEdgeRefCounts.find(oid) == sp.meshedEdgeRefCounts.end()) { + if (state.meshedEdgeRefCounts.find(oid) == state.meshedEdgeRefCounts.end()) { auto ed = extractEdgeNodes(gt); if (!ed.empty()) { - sp.meshedEdgesCache[oid] = std::move(ed); - sp.meshedEdgeRefCounts[oid] = 1; // 首次创建 + state.meshedEdgesCache[oid] = std::move(ed); + state.meshedEdgeRefCounts[oid] = 1; // 首次创建 nNew++; } } else { - sp.meshedEdgeRefCounts[oid]++; // 被另一个面复用 + state.meshedEdgeRefCounts[oid]++; // 被另一个面复用 nShared++; } } - spdlog::info("GmshMesh: {} new edges stored, {} edges reused (total cached: {})", nNew, nShared, sp.meshedEdgeRefCounts.size()); + spdlog::info("GmshMesh: {} new edges stored, {} edges reused (total cached: {})", nNew, nShared, state.meshedEdgeRefCounts.size()); } -void mergeMeshResult(MeshData& mesh_data, const SingleFaceMeshResult& result) +void mergeMeshResult(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, const SingleFaceMeshResult& result) { if (!result.success) return; @@ -673,9 +692,9 @@ void mergeMeshResult(MeshData& mesh_data, const SingleFaceMeshResult& result) if (mesh_data.face_vertices_offset_.empty()) mesh_data.face_vertices_offset_.push_back(0); - TempNodeLookup lookup(mesh_data.vertex_positions_, 1e-7); + TempNodeLookup lookup(mesh_data, geometry, model_layer, 1e-7); - std::vector localToGlobal(result.vertices.size()); + std::vector localToGlobal(result.vertices.size()); for (size_t i = 0; i < result.vertices.size(); ++i) { localToGlobal[i] = lookup.getOrInsert( result.vertices[i][0], @@ -688,7 +707,7 @@ void mergeMeshResult(MeshData& mesh_data, const SingleFaceMeshResult& result) size_t end = result.face_vertices_offset[i + 1]; for (size_t j = start; j < end; ++j) { mesh_data.face_vertices_.push_back( - static_cast(localToGlobal[result.face_vertices[j]])); + localToGlobal[result.face_vertices[j]]); } mesh_data.face_vertices_offset_.push_back( static_cast(mesh_data.face_vertices_.size())); @@ -703,39 +722,40 @@ void mergeMeshResult(MeshData& mesh_data, const SingleFaceMeshResult& result) // 公开接口 bool IncrementalMeshTools::initMeshing(const std::string& stepFile, - SplineData& spline) + GeometryData& geometry) { - spline.clearMeshCache(); - spline.rootShape = std::make_unique(loadStep(stepFile)); - if (spline.rootShape->IsNull()) { + geometry.gmsh_mesh_state.clear(); + geometry.rootShape = std::make_unique(loadStep(stepFile)); + if (geometry.rootShape->IsNull()) { spdlog::error("Failed to load: {}", stepFile); return false; } - spline.meshContext = std::make_unique(*spline.rootShape); + geometry.gmsh_mesh_state.meshContext = std::make_unique(*geometry.rootShape); spdlog::info("Loaded: {} faces, {} global edges", - spline.meshContext->faceCount(), - spline.meshContext->globalEdgeCount()); - return spline.meshContext->faceCount() > 0; + geometry.gmsh_mesh_state.meshContext->faceCount(), + geometry.gmsh_mesh_state.meshContext->globalEdgeCount()); + return geometry.gmsh_mesh_state.meshContext->faceCount() > 0; } SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( - MeshData& mesh_data, SplineData& sp, std::size_t faceIndex, double meshSize) + MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex, double meshSize) { SingleFaceMeshResult result; + auto& state = geometry.gmsh_mesh_state; - if (!sp.meshContext) { + if (!state.meshContext) { spdlog::error("meshContext null, call initMeshing first"); return result; } - if (faceIndex >= sp.meshContext->faceCount()) { + if (faceIndex >= state.meshContext->faceCount()) { spdlog::error("faceIndex {} out of range", faceIndex); return result; } spdlog::info("=== Meshing face {}/{} (size={:.6f}) ===", - faceIndex + 1, sp.meshContext->faceCount(), meshSize); + faceIndex + 1, state.meshContext->faceCount(), meshSize); - TopoDS_Face face = sp.meshContext->getFaceByIndex(faceIndex); + TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); if (face.IsNull()) { spdlog::error("Face {} is null or invalid", faceIndex); return result; @@ -760,15 +780,15 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } int faceTag = faceDimTags[0].second; - auto gmshToOcc = matchGmshToOCCEdges(face, *sp.meshContext); + auto gmshToOcc = matchGmshToOCCEdges(face, *state.meshContext); std::size_t nodeCounter = 1, elemCounter = 1; int shared = 0, free = 0; std::map vtxNodeMap; for (auto& [gt, oid] : gmshToOcc) { - if (sp.meshedEdgeRefCounts.count(oid) > 0) { - injectConstrainedEdge(gt, oid, sp.meshedEdgesCache.at(oid), + if (state.meshedEdgeRefCounts.count(oid) > 0) { + injectConstrainedEdge(gt, oid, state.meshedEdgesCache.at(oid), nodeCounter, elemCounter, vtxNodeMap); shared++; } else { @@ -787,7 +807,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::model::mesh::generate(2); } catch (const std::exception& e) { spdlog::error(" Mesh failed: {}", e.what()); - storeNewEdges(sp, gmshToOcc); + storeNewEdges(geometry, gmshToOcc); gmsh::finalize(); return result; } @@ -805,29 +825,29 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } if (!has) { spdlog::warn(" No surface elements"); - storeNewEdges(sp, gmshToOcc); + storeNewEdges(geometry, gmshToOcc); gmsh::finalize(); return result; } } result = extractFaceMesh(faceTag); - storeNewEdges(sp, gmshToOcc); - sp.meshedFacesCache[faceIndex] = result; // 缓存面结果 + storeNewEdges(geometry, gmshToOcc); + state.meshedFacesCache[faceIndex] = result; // 缓存面结果 if (result.success) { - mergeMeshResult(mesh_data, result); + mergeMeshResult(mesh_data, geometry, model_layer, result); } gmsh::finalize(); return result; } -double IncrementalMeshTools::estimateMeshSize(const SplineData& sp) +double IncrementalMeshTools::estimateMeshSize(const GeometryData& geometry) { - if (!sp.rootShape || sp.rootShape->IsNull()) + if (!geometry.rootShape || geometry.rootShape->IsNull()) return 10.0; GProp_GProps props; - BRepGProp::SurfaceProperties(*sp.rootShape, props); + BRepGProp::SurfaceProperties(*geometry.rootShape, props); double area = props.Mass(); if (area > 0) { double s = std::sqrt(area) / 10.0; @@ -840,14 +860,14 @@ double IncrementalMeshTools::estimateMeshSize(const SplineData& sp) return 10.0; } -std::size_t IncrementalMeshTools::faceCount(const SplineData& sp) +std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) { - return sp.meshContext ? sp.meshContext->faceCount() : 0; + return geometry.gmsh_mesh_state.meshContext ? geometry.gmsh_mesh_state.meshContext->faceCount() : 0; } -std::size_t IncrementalMeshTools::meshedEdgeCount(const SplineData& sp) +std::size_t IncrementalMeshTools::meshedEdgeCount(const GeometryData& geometry) { - return sp.meshedEdgeRefCounts.size(); + return geometry.gmsh_mesh_state.meshedEdgeRefCounts.size(); } bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath) @@ -873,7 +893,7 @@ bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, c return true; } -bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const std::filesystem::path& filepath) +bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const GeometryData& geometry, const std::filesystem::path& filepath) { std::ofstream ofs(filepath); if (!ofs.is_open()) @@ -885,58 +905,71 @@ bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const std::filesyst for (const auto& v : res.vertex_positions_) ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; + std::unordered_map globalToLocal; + const auto& globalIds = geometry.gmsh_mesh_state.local_to_global_point_ids; + for (std::size_t i = 0; i < globalIds.size(); ++i) { + globalToLocal[globalIds[i]] = i + 1; + } + for (size_t i = 0; i + 1 < res.face_vertices_offset_.size(); ++i) { size_t start = res.face_vertices_offset_[i]; size_t end = res.face_vertices_offset_[i + 1]; ofs << "f"; - for (size_t j = start; j < end; ++j) - ofs << " " << (res.face_vertices_[j] + 1); + for (size_t j = start; j < end; ++j) { + auto it = globalToLocal.find(res.face_vertices_[j]); + if (it == globalToLocal.end()) { + spdlog::error("GmshMesh: missing local point for global id {}", res.face_vertices_[j]); + return false; + } + ofs << " " << it->second; + } ofs << "\n"; } return true; } -bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, SplineData& spline, std::size_t faceIndex) +bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex) { + auto& state = geometry.gmsh_mesh_state; // 检查是否存在该面的缓存 - auto it = spline.meshedFacesCache.find(faceIndex); - if (it == spline.meshedFacesCache.end()) { + auto it = state.meshedFacesCache.find(faceIndex); + if (it == state.meshedFacesCache.end()) { spdlog::warn("Face {} is not meshed or not cached.", faceIndex); return false; } // 释放面的边界边(更新引用计数并在归零时移除) - if (spline.meshContext) { - TopoDS_Face face = spline.meshContext->getFaceByIndex(faceIndex); - auto occEdges = spline.meshContext->getFaceEdgeIds(face); + if (state.meshContext) { + TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); + auto occEdges = state.meshContext->getFaceEdgeIds(face); for (int globalEdgeId : occEdges) { - auto refIt = spline.meshedEdgeRefCounts.find(globalEdgeId); - if (refIt != spline.meshedEdgeRefCounts.end()) { + auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); + if (refIt != state.meshedEdgeRefCounts.end()) { refIt->second--; if (refIt->second <= 0) { - spline.meshedEdgeRefCounts.erase(refIt); - spline.meshedEdgesCache.erase(globalEdgeId); + state.meshedEdgeRefCounts.erase(refIt); + state.meshedEdgesCache.erase(globalEdgeId); spdlog::info("Edge {} cache cleaned up.", globalEdgeId); } } } } - spline.meshedFacesCache.erase(it); + state.meshedFacesCache.erase(it); // 重构meshdata 暂时重建面索引 mesh_data.face_vertices_.clear(); mesh_data.face_vertices_offset_.clear(); mesh_data.face_vertices_offset_.push_back(0); - TempNodeLookup lookup(mesh_data.vertex_positions_, 1e-7); + TempNodeLookup lookup(mesh_data, geometry, model_layer, 1e-7); // 遍历目前还保留的所有有效面,按序装入MeshData - for (const auto& [fIdx, faceMeshResult] : spline.meshedFacesCache) { + for (const auto& [fIdx, faceMeshResult] : state.meshedFacesCache) { if (!faceMeshResult.success) continue; - std::vector localToGlobal(faceMeshResult.vertices.size()); + std::vector localToGlobal(faceMeshResult.vertices.size()); for (size_t i = 0; i < faceMeshResult.vertices.size(); ++i) { localToGlobal[i] = lookup.getOrInsert( faceMeshResult.vertices[i][0], @@ -949,7 +982,7 @@ bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, SplineData& splin size_t end = faceMeshResult.face_vertices_offset[i + 1]; for (size_t j = start; j < end; ++j) { mesh_data.face_vertices_.push_back( - static_cast(localToGlobal[faceMeshResult.face_vertices[j]])); + localToGlobal[faceMeshResult.face_vertices[j]]); } mesh_data.face_vertices_offset_.push_back( static_cast(mesh_data.face_vertices_.size())); @@ -962,11 +995,11 @@ bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, SplineData& splin } SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( - MeshData& mesh_data, SplineData& sp, std::size_t faceIndex, double meshSize) + MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex, double meshSize) { - if (sp.meshedFacesCache.find(faceIndex) != sp.meshedFacesCache.end()) { + if (geometry.gmsh_mesh_state.meshedFacesCache.find(faceIndex) != geometry.gmsh_mesh_state.meshedFacesCache.end()) { spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); - deleteFaceMesh(mesh_data, sp, faceIndex); + deleteFaceMesh(mesh_data, geometry, model_layer, faceIndex); } - return meshSingleFace(mesh_data, sp, faceIndex, meshSize); + return meshSingleFace(mesh_data, geometry, model_layer, faceIndex, meshSize); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index c3c924b..a304420 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -4,36 +4,39 @@ #include #include #include -#include "SplineData.h" +#include "GeometryData.h" struct MeshData; +class ModelLayer; namespace IncrementalMeshTools { -bool initMeshing(const std::string& stepFile, SplineData& spline); +bool initMeshing(const std::string& stepFile, GeometryData& geometry); SingleFaceMeshResult meshSingleFace( MeshData& mesh_data, - SplineData& spline, + GeometryData& geometry, + ModelLayer& model_layer, std::size_t faceIndex, double meshSize); SingleFaceMeshResult remeshSingleFace( MeshData& mesh_data, - SplineData& spline, + GeometryData& geometry, + ModelLayer& model_layer, std::size_t faceIndex, double meshSize); -bool deleteFaceMesh(MeshData& mesh_data, SplineData& spline, std::size_t faceIndex); +bool deleteFaceMesh(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex); -double estimateMeshSize(const SplineData& spline); +double estimateMeshSize(const GeometryData& geometry); -std::size_t faceCount(const SplineData& spline); +std::size_t faceCount(const GeometryData& geometry); -std::size_t meshedEdgeCount(const SplineData& spline); +std::size_t meshedEdgeCount(const GeometryData& geometry); bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); -bool writeMeshObj(const MeshData& res, const std::filesystem::path& filepath); +bool writeMeshObj(const MeshData& res, const GeometryData& geometry, const std::filesystem::path& filepath); -} // namespace IncrementalMeshTools \ No newline at end of file +} // namespace IncrementalMeshTools diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt index 55ab140..d8f79bd 100644 --- a/plugins/algo/GmshPlugin/test/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -1,5 +1,5 @@ precess_add_test(GmshPluginTest GmshPluginTest.cpp) -precess_test_link_libraries(GmshPluginTest +precess_test_link_libraries(GmshPluginTest Data Systems Core @@ -11,23 +11,29 @@ precess_test_link_libraries(GmshPluginTest GmshPluginlib ) -# GmshVtkExample add_executable(GmshVtkExample "GmshVtkExample.cpp") target_link_libraries(GmshVtkExample PRIVATE gmsh::shared - vtkPart TestDeps - VTK::InteractionStyle VTK::CommonColor VTK::RenderingCore VTK::RenderingUI VTK::RenderingOpenGL2 VTK::FiltersGeometry - TKBRep # BRep_Builder, BRepTools - TKernel # Standard_* 基础类型 - TKG3d # gp_* 准则基础 - TKDESTEP # STEPControl_Reader - TKXSBase # STEP/IGES 转换基础 - TKTopAlgo # TopExp_Explorer + Data GmshPluginlib + VTK::InteractionStyle + VTK::CommonColor + VTK::RenderingCore + VTK::RenderingUI + VTK::RenderingOpenGL2 + VTK::FiltersGeometry +) +vtk_module_autoinit(TARGETS GmshVtkExample MODULES + VTK::InteractionStyle + VTK::CommonColor + VTK::RenderingCore + VTK::RenderingUI + VTK::RenderingOpenGL2 + VTK::FiltersGeometry ) add_custom_command(TARGET GmshVtkExample POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ COMMENT "Copying gmsh.dll to GmshVtkExample output directory" -) \ No newline at end of file +) diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index e707c55..69aca38 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -1,14 +1,14 @@ #include #include "GmshMeshHandler.h" -#include "IncrementalMeshContext.h" -#include "IncrementalMeshTools.h" #include "ArgObject.h" +#include "ComponentData.h" +#include "GeometryData.h" +#include "IncrementalMeshContext.h" #include "ModelData.h" #include "ModelIOSystemBase.h" -#include "ModelOperator.h" -#include "SplineData.h" +#include "ModelLayer.h" #include #include @@ -17,16 +17,25 @@ namespace { -// 模拟的 IO 系统,避免在测试中涉及真实的界面交互或不可控流 +// 测试用 IO 系统,只记录插件读写请求,避免真实导入导出影响用例状态。 class MockIOSystem : public systems::io::ModelIOSystemBase { public: void read(const std::filesystem::path& path, const std::string& file_type, const std::vector& args) override { spdlog::info("[MockIO] read file: {}", path.string()); } + void write(Index model, const std::filesystem::path& path, const std::string& file_type, const std::vector& args) override { - spdlog::info("[MockIO] write to: {}", path.string()); + spdlog::info("[MockIO] write model to: {}", path.string()); + } + + void writeComponents(const std::vector& component_ids, + const std::filesystem::path& path, + const std::string& file_type, + const std::vector& args) override + { + spdlog::info("[MockIO] write components to: {}", path.string()); } }; @@ -36,43 +45,38 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") { spdlog::set_level(spdlog::level::info); - // 1. 使用 OCC 构造一个 10x10x10 的立方体形状 BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0); boxMaker.Build(); REQUIRE(boxMaker.IsDone() == true); - TopoDS_Shape cubeShape = boxMaker.Shape(); - // 2. 初始化 SplineData 及持有它的 ModelData - auto splineData = std::make_unique(); - splineData->rootShape = std::make_unique(cubeShape); + auto geometryData = std::make_unique(); + geometryData->rootShape = std::make_unique(boxMaker.Shape()); + + ModelLayer modelLayer; + Index modelId = modelLayer.addModel(std::make_unique(std::move(geometryData))); + auto componentIds = modelLayer.getComponentIds(modelId); + REQUIRE(componentIds.size() == 1); - ModelData modelData(std::move(splineData)); - ModelOperator op(1, modelData); + auto componentOp = modelLayer.getComponentOperator(componentIds[0]); + REQUIRE(componentOp.has_value()); - // 3. 构建模拟的 IO 系统和上下文环境 MockIOSystem mockIo; - systems::algo::HandlerContext context { mockIo, op }; + systems::algo::HandlerContext context { mockIo, *componentOp }; - // 4. 准备入参: - // Arg 0: 面索引 "0" - // Arg 1: 网格尺寸 "2.0" std::vector args; args.push_back(core::ArgObject::create("0")); args.push_back(core::ArgObject::create("2.0")); - // 5. 调用执行插件方法 systems::algo::GmshMeshHandler handler; - std::any result = handler.execute(context, args); - - // 6. 后置断言:验证执行后的内部状态变化 - SplineData* sp = op.data().asSplineData(); - REQUIRE(sp != nullptr); - REQUIRE(sp->meshContext != nullptr); - - // 立方体应当被提取到了 6 个面 - REQUIRE(sp->meshContext->faceCount() == 6); - - // 由于面 0 已经成功完成了网格划分,其关联的 4 条自由边界应该被缓存起来了 - REQUIRE(sp->meshedEdgeRefCounts.size() == 4); - REQUIRE(sp->meshedEdgesCache.size() == 4); -} \ No newline at end of file + handler.execute(context, args); + + ComponentData* comp = modelLayer.findComponent(componentIds[0]); + REQUIRE(comp != nullptr); + REQUIRE(comp->mesh != nullptr); + REQUIRE(comp->geometry != nullptr); + REQUIRE(comp->geometry->gmsh_mesh_state.meshContext != nullptr); + REQUIRE(comp->geometry->gmsh_mesh_state.meshContext->faceCount() == 6); + REQUIRE(comp->geometry->gmsh_mesh_state.meshedEdgeRefCounts.size() == 4); + REQUIRE(comp->geometry->gmsh_mesh_state.meshedEdgesCache.size() == 4); + REQUIRE_FALSE(comp->geometry->gmsh_mesh_state.local_to_global_point_ids.empty()); +} diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index eb36596..09ded56 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -1,68 +1,156 @@ #include +#include "GeometryData.h" +#include "IncrementalMeshContext.h" +#include "IncrementalMeshTools.h" +#include "MeshData.h" +#include "ModelLayer.h" + +#include + #include -#include +#include #include +#include #include -#include "IncrementalMeshTools.h" -#include "IncrementalMeshTools.cpp" -#include "SplineData.h" -#include "MeshData.h" -#include "MeshActor.h" -#include "ModelData.h" -#include "MakeMeshDataVtk.h" -#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -// ================================================================ -// 保存 -// ================================================================ -static void saveMesh(const MeshData& md, const std::string& filename) +// Runtime state shared by the VTK key callback and the example main loop. +struct AppContext { + GeometryData geometry; + MeshData meshData; + ModelLayer modelLayer; + vtkSmartPointer polyData; + vtkRenderWindow* window {}; + std::size_t currentIndex { 0 }; + double meshSize { 10.0 }; +}; + +// Builds a local file/render index for mesh vertices whose faces store global point ids. +static std::unordered_map buildGlobalToLocalPointMap(const GeometryData& geometry) +{ + std::unordered_map globalToLocal; + const auto& globalIds = geometry.gmsh_mesh_state.local_to_global_point_ids; + for (std::size_t i = 0; i < globalIds.size(); ++i) { + globalToLocal[globalIds[i]] = i; + } + return globalToLocal; +} + +// Reloads vtkPolyData from MeshData without depending on app/render classes. +static void reloadPolyData(AppContext& ctx) +{ + auto points = vtkSmartPointer::New(); + for (const auto& p : ctx.meshData.vertex_positions_) { + points->InsertNextPoint(p[0], p[1], p[2]); + } + + auto polys = vtkSmartPointer::New(); + auto globalToLocal = buildGlobalToLocalPointMap(ctx.geometry); + + for (std::size_t i = 0; i + 1 < ctx.meshData.face_vertices_offset_.size(); ++i) { + std::size_t begin = ctx.meshData.face_vertices_offset_[i]; + std::size_t end = ctx.meshData.face_vertices_offset_[i + 1]; + vtkIdType count = static_cast(end - begin); + if (count <= 0) + continue; + + polys->InsertNextCell(count); + for (std::size_t j = begin; j < end; ++j) { + auto it = globalToLocal.find(ctx.meshData.face_vertices_[j]); + if (it == globalToLocal.end()) { + spdlog::warn("Missing local point for global id {}", ctx.meshData.face_vertices_[j]); + polys->InsertCellPoint(0); + } else { + polys->InsertCellPoint(static_cast(it->second)); + } + } + } + + ctx.polyData->SetPoints(points); + ctx.polyData->SetPolys(polys); + ctx.polyData->Modified(); + ctx.window->Render(); +} + +// Resets generated mesh and Gmsh caches while keeping the loaded CAD shape. +static void resetGeneratedMesh(AppContext& ctx) { - if (md.vertex_positions_.empty()) { + ctx.meshData.init(); + ctx.geometry.gmsh_mesh_state.clear(); + if (ctx.geometry.rootShape) { + ctx.geometry.gmsh_mesh_state.meshContext = + std::make_unique(*ctx.geometry.rootShape); + } + ctx.currentIndex = 0; + ctx.meshSize = IncrementalMeshTools::estimateMeshSize(ctx.geometry); + reloadPolyData(ctx); +} + +// Saves the example mesh by translating global point ids back to local file node ids. +static void saveMesh(const MeshData& mesh, const GeometryData& geometry, const std::string& filename) +{ + if (mesh.vertex_positions_.empty()) { spdlog::warn("No mesh data to save."); return; } + try { gmsh::initialize(); gmsh::model::add("merged"); int tag = gmsh::model::addDiscreteEntity(2); - std::vector nt(md.vertex_positions_.size()); - std::vector nc(md.vertex_positions_.size() * 3); - for (size_t i = 0; i < md.vertex_positions_.size(); ++i) { - nt[i] = i + 1; - nc[i * 3] = md.vertex_positions_[i][0]; - nc[i * 3 + 1] = md.vertex_positions_[i][1]; - nc[i * 3 + 2] = md.vertex_positions_[i][2]; + std::vector nodeTags(mesh.vertex_positions_.size()); + std::vector nodeCoords(mesh.vertex_positions_.size() * 3); + auto globalToLocal = buildGlobalToLocalPointMap(geometry); + + for (std::size_t i = 0; i < mesh.vertex_positions_.size(); ++i) { + nodeTags[i] = i + 1; + nodeCoords[i * 3] = mesh.vertex_positions_[i][0]; + nodeCoords[i * 3 + 1] = mesh.vertex_positions_[i][1]; + nodeCoords[i * 3 + 2] = mesh.vertex_positions_[i][2]; } - gmsh::model::mesh::addNodes(2, tag, nt, nc); - - std::vector tt, tn, qt, qn; - std::size_t ec = 1; - for (size_t i = 0; i + 1 < md.face_vertices_offset_.size(); ++i) { - size_t s = md.face_vertices_offset_[i]; - size_t e = md.face_vertices_offset_[i + 1]; - size_t c = e - s; - if (c == 3) { - tt.push_back(ec++); - for (size_t j = s; j < e; ++j) - tn.push_back(md.face_vertices_[j] + 1); - } else if (c == 4) { - qt.push_back(ec++); - for (size_t j = s; j < e; ++j) - qn.push_back(md.face_vertices_[j] + 1); + gmsh::model::mesh::addNodes(2, tag, nodeTags, nodeCoords); + + std::vector triTags, triNodes, quadTags, quadNodes; + std::size_t elemTag = 1; + for (std::size_t i = 0; i + 1 < mesh.face_vertices_offset_.size(); ++i) { + std::size_t begin = mesh.face_vertices_offset_[i]; + std::size_t end = mesh.face_vertices_offset_[i + 1]; + std::size_t count = end - begin; + if (count != 3 && count != 4) + continue; + + auto& tags = count == 3 ? triTags : quadTags; + auto& nodes = count == 3 ? triNodes : quadNodes; + tags.push_back(elemTag++); + for (std::size_t j = begin; j < end; ++j) { + auto it = globalToLocal.find(mesh.face_vertices_[j]); + if (it == globalToLocal.end()) { + spdlog::error("Missing local node for global id {}", mesh.face_vertices_[j]); + gmsh::finalize(); + return; + } + nodes.push_back(it->second + 1); } } - if (!tt.empty()) - gmsh::model::mesh::addElementsByType(tag, 2, tt, tn); - if (!qt.empty()) - gmsh::model::mesh::addElementsByType(tag, 3, qt, qn); + + if (!triTags.empty()) + gmsh::model::mesh::addElementsByType(tag, 2, triTags, triNodes); + if (!quadTags.empty()) + gmsh::model::mesh::addElementsByType(tag, 3, quadTags, quadNodes); gmsh::write(filename); spdlog::info("Saved: {}", std::filesystem::absolute(filename).string()); @@ -74,72 +162,38 @@ static void saveMesh(const MeshData& md, const std::string& filename) } } -// ================================================================ -// AppContext + 回调 -// ================================================================ -struct AppContext { - SplineData* spline; - MeshActor* actor; - vtkRenderWindow* window; - - // 累积状态 - MeshData meshData; - std::size_t currentIndex = 0; - double meshSize = 10.0; - - void init(double estimatedSize) - { - meshData.clear(); - currentIndex = 0; - meshSize = estimatedSize; - - meshData.face_vertices_offset_.push_back(0); - if (meshData.solid_vertices_offset_.empty()) - meshData.solid_vertices_offset_.push_back(0); - if (meshData.solid_faces_vertices_offset_.empty()) - meshData.solid_faces_vertices_offset_.push_back(0); - if (meshData.solid_faces_offset_.empty()) - meshData.solid_faces_offset_.push_back(0); - } -}; - -static void KeyPressCallback(vtkObject* caller, unsigned long, - void* clientData, void*) +static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, void*) { auto* ctx = static_cast(clientData); auto* interactor = static_cast(caller); std::string key = interactor->GetKeySym(); - std::size_t total = IncrementalMeshTools::faceCount(*ctx->spline); + std::size_t total = IncrementalMeshTools::faceCount(ctx->geometry); if (key == "space") { if (ctx->currentIndex >= total) { - spdlog::info("All faces meshed!"); + spdlog::info("All faces meshed."); return; } - spdlog::info(">>> Face {}/{} (size={:.4f})", - ctx->currentIndex + 1, total, ctx->meshSize); - auto res = IncrementalMeshTools::meshSingleFace( - ctx->meshData, *ctx->spline, ctx->currentIndex, ctx->meshSize); + auto result = IncrementalMeshTools::meshSingleFace( + ctx->meshData, ctx->geometry, ctx->modelLayer, ctx->currentIndex, ctx->meshSize); ctx->currentIndex++; - if (res.success) { - ctx->actor->loadModelData(MakeMeshDataVtk(ctx->meshData)); - ctx->window->Render(); + if (result.success) { + reloadPolyData(*ctx); + if (ctx->meshSize < 50.0) + ctx->meshSize *= 1.5; + spdlog::info("nodes={}, cached_edges={}, next_size={:.4f}", + ctx->meshData.vertex_positions_.size(), + IncrementalMeshTools::meshedEdgeCount(ctx->geometry), + ctx->meshSize); } - - if (ctx->meshSize < 50.0) - ctx->meshSize *= 1.5; - spdlog::info(" nodes={}, edges={}, next_size={:.4f}", - ctx->meshData.vertex_positions_.size(), - IncrementalMeshTools::meshedEdgeCount(*ctx->spline), - ctx->meshSize); - } else if (key == "s" || key == "S") - saveMesh(ctx->meshData, "final_mesh.msh"); - else if (key == "r" || key == "R") { - ctx->meshSize = IncrementalMeshTools::estimateMeshSize(*ctx->spline); - spdlog::info("Clean vertices,Reset size: {:.4f}", ctx->meshSize); + } else if (key == "s" || key == "S") { + saveMesh(ctx->meshData, ctx->geometry, "final_mesh.msh"); + } else if (key == "r" || key == "R") { + resetGeneratedMesh(*ctx); + spdlog::info("Reset mesh, size={:.4f}", ctx->meshSize); } else if (key == "plus" || key == "equal") { ctx->meshSize *= 2.0; spdlog::info("Size: {:.4f}", ctx->meshSize); @@ -148,62 +202,57 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, spdlog::info("Size: {:.4f}", ctx->meshSize); } else if (key == "d" || key == "D") { if (ctx->currentIndex == 0) { - spdlog::warn("没有可以删除的网格面了!"); - } else { - ctx->currentIndex--; - spdlog::info("Delete mesh for face: {}", ctx->currentIndex); - - if (ctx->currentIndex == 0) { - ctx->init(ctx->meshSize); - } else { - IncrementalMeshTools::deleteFaceMesh(ctx->meshData, *ctx->spline, ctx->currentIndex); - } - - ctx->actor->loadModelData(MakeMeshDataVtk(ctx->meshData)); - ctx->window->Render(); + spdlog::warn("No meshed face to delete."); + return; + } + ctx->currentIndex--; + if (IncrementalMeshTools::deleteFaceMesh(ctx->meshData, ctx->geometry, ctx->modelLayer, ctx->currentIndex)) { + reloadPolyData(*ctx); } } else if (key == "h" || key == "H") { spdlog::info("SPACE=mesh, S=save, R=reset, +/-=size, D=delete, H=help"); } } -// ================================================================ -// Main -// ================================================================ int main(int argc, char* argv[]) { spdlog::set_level(spdlog::level::info); - SplineData spline; - - std::string path = (argc > 1) - ? argv[1] - : "E:/VSProject/_models/models/airplane.stp"; - + std::string path = argc > 1 ? argv[1] : "E:/VSProject/_models/models/airplane.stp"; spdlog::info("Loading: {}", path); - if (!IncrementalMeshTools::initMeshing(path, spline)) { + AppContext ctx; + ctx.polyData = vtkSmartPointer::New(); + ctx.meshData.init(); + + if (!IncrementalMeshTools::initMeshing(path, ctx.geometry)) { spdlog::error("Cannot import: {}", path); return 1; } + ctx.meshSize = IncrementalMeshTools::estimateMeshSize(ctx.geometry); auto renderer = vtkSmartPointer::New(); renderer->SetBackground(0.1, 0.15, 0.2); + auto mapper = vtkSmartPointer::New(); + mapper->SetInputData(ctx.polyData); + + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->GetProperty()->SetColor(0.85, 0.88, 0.92); + actor->GetProperty()->EdgeVisibilityOn(); + actor->GetProperty()->SetEdgeColor(0.1, 0.2, 0.35); + renderer->AddActor(actor); + auto window = vtkSmartPointer::New(); window->AddRenderer(renderer); window->SetSize(1024, 768); window->SetWindowName("GmshMesh Test"); + ctx.window = window; auto interactor = vtkSmartPointer::New(); interactor->SetRenderWindow(window); - auto actor = std::make_shared( - renderer, true, true, ModelRenderMode::Face); - - AppContext ctx{ &spline, actor.get(), window }; - ctx.init(IncrementalMeshTools::estimateMeshSize(spline)); - auto cb = vtkSmartPointer::New(); cb->SetCallback(KeyPressCallback); cb->SetClientData(&ctx); @@ -212,4 +261,4 @@ int main(int argc, char* argv[]) window->Render(); interactor->Start(); return 0; -} \ No newline at end of file +} -- Gitee From 2ce03cc5c89f1f568627c727a2290b28a8323f4e Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 31 May 2026 15:16:21 +0800 Subject: [PATCH 03/21] =?UTF-8?q?refeactor(gmsh)=EF=BC=9A=E5=B0=86?= =?UTF-8?q?=E5=9C=A8GeometryData=E9=87=8C=E5=A2=9E=E5=8A=A0=E7=9A=84=20?= =?UTF-8?q?=E8=BE=B9=E9=9D=A2=E7=BD=91=E6=A0=BC=E6=95=B0=E6=8D=AE=E7=AD=89?= =?UTF-8?q?=E7=A7=BB=E5=88=B0gmsh=E6=8F=92=E4=BB=B6=E9=87=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/data/CMakeLists.txt | 1 - model/data/GeometryData.cpp | 13 --- model/data/GeometryData.h | 38 +------- plugins/algo/GmshPlugin/CMakeLists.txt | 4 +- .../GmshPlugin/GmshIncrementalMeshState.cpp | 15 ++++ .../GmshPlugin/GmshIncrementalMeshState.h | 42 +++++++++ plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 51 +++++++---- plugins/algo/GmshPlugin/GmshMeshHandler.h | 12 ++- .../GmshPlugin}/IncrementalMeshContext.cpp | 0 .../algo/GmshPlugin}/IncrementalMeshContext.h | 0 .../algo/GmshPlugin/IncrementalMeshTools.cpp | 87 +++++++++++-------- .../algo/GmshPlugin/IncrementalMeshTools.h | 17 ++-- .../algo/GmshPlugin/test/GmshPluginTest.cpp | 9 +- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 27 +++--- 14 files changed, 187 insertions(+), 129 deletions(-) create mode 100644 plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp create mode 100644 plugins/algo/GmshPlugin/GmshIncrementalMeshState.h rename {model/data => plugins/algo/GmshPlugin}/IncrementalMeshContext.cpp (100%) rename {model/data => plugins/algo/GmshPlugin}/IncrementalMeshContext.h (100%) diff --git a/model/data/CMakeLists.txt b/model/data/CMakeLists.txt index 6faf7b9..7a3a49b 100644 --- a/model/data/CMakeLists.txt +++ b/model/data/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(Data STATIC ModelData.cpp MeshData.cpp GeometryData.cpp - IncrementalMeshContext.cpp ModelLayer.cpp ModelOperator.cpp GeometryRegistry.cpp diff --git a/model/data/GeometryData.cpp b/model/data/GeometryData.cpp index 166db44..459e59b 100644 --- a/model/data/GeometryData.cpp +++ b/model/data/GeometryData.cpp @@ -1,20 +1,7 @@ #include "GeometryData.h" #include "GeometryDataVtk.h" -#include "IncrementalMeshContext.h" #include -GmshIncrementalMeshState::GmshIncrementalMeshState() = default; -GmshIncrementalMeshState::~GmshIncrementalMeshState() = default; - -void GmshIncrementalMeshState::clear() -{ - meshContext.reset(); - meshedEdgesCache.clear(); - meshedFacesCache.clear(); - meshedEdgeRefCounts.clear(); - local_to_global_point_ids.clear(); -} - GeometryData::GeometryData() = default; GeometryData::~GeometryData() = default; diff --git a/model/data/GeometryData.h b/model/data/GeometryData.h index 87a4113..183f0f4 100644 --- a/model/data/GeometryData.h +++ b/model/data/GeometryData.h @@ -1,51 +1,15 @@ #pragma once #include #include -#include -#include -#include #include "GeometrySubshapeIndex.h" class TopoDS_Shape; struct GeometryDataVtk; -class IncrementalMeshContext; - -// 保存一条已生成网格的 CAD 边,供相邻面复用边界节点。 -struct MeshedEdgeData { - std::vector coords; - std::vector paramCoords; - - std::size_t nodeCount() const { return coords.size() / 3; } - bool empty() const { return coords.empty(); } -}; - -// 保存单个 CAD 面的网格结果,面删除和重划分时用它重建整体 MeshData。 -struct SingleFaceMeshResult { - std::vector> vertices; - std::vector face_vertices; - std::vector face_vertices_offset; - bool success { false }; -}; - -// GeometryData 上的 Gmsh 增量网格状态,生命周期跟随当前 CAD component。 -struct GmshIncrementalMeshState { - GmshIncrementalMeshState(); - ~GmshIncrementalMeshState(); - - std::unique_ptr meshContext; - std::map meshedEdgesCache; - std::map meshedFacesCache; - std::map meshedEdgeRefCounts; - std::vector local_to_global_point_ids; - - // 清空当前 CAD component 的增量网格缓存,不影响 GeometryData::rootShape。 - void clear(); -}; // 以后需要控制点 / 曲率等,可继续添加字段 struct GeometryData { GeometryData(); - ~GeometryData(); + ~GeometryData(); std::unique_ptr rootShape; // 读取 STEP/IGES 后的拓扑根 GeometrySubshapeIndex index; void ensureIndexBuilt(GeometryRegistry& reg); diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index b7a87c9..5bcb13e 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -3,6 +3,8 @@ find_package(gmsh REQUIRED) precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" + "GmshIncrementalMeshState.cpp" + "IncrementalMeshContext.cpp" "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" ) @@ -17,4 +19,4 @@ precess_plugin_link_libraries(GmshPlugin ) -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp new file mode 100644 index 0000000..d9ac095 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp @@ -0,0 +1,15 @@ +#include "GmshIncrementalMeshState.h" + +#include "IncrementalMeshContext.h" + +GmshIncrementalMeshState::GmshIncrementalMeshState() = default; +GmshIncrementalMeshState::~GmshIncrementalMeshState() = default; + +void GmshIncrementalMeshState::clear() +{ + meshContext.reset(); + meshedEdgesCache.clear(); + meshedFacesCache.clear(); + meshedEdgeRefCounts.clear(); + local_to_global_point_ids.clear(); +} diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h new file mode 100644 index 0000000..d762f59 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Core.h" + +#include +#include +#include +#include + +class IncrementalMeshContext; + +// 保存一条已生成网格的 CAD 边,供相邻面复用边界节点。 +struct MeshedEdgeData { + std::vector coords; + std::vector paramCoords; + + std::size_t nodeCount() const { return coords.size() / 3; } + bool empty() const { return coords.empty(); } +}; + +// 保存单个 CAD 面的网格结果,面删除和重划分时用它重建整体 MeshData。 +struct SingleFaceMeshResult { + std::vector> vertices; + std::vector face_vertices; + std::vector face_vertices_offset; + bool success { false }; +}; + +// 保存一个 component 的 Gmsh 增量网格状态,由 Gmsh 插件管理生命周期。 +struct GmshIncrementalMeshState { + GmshIncrementalMeshState(); + ~GmshIncrementalMeshState(); + + std::unique_ptr meshContext; + std::map meshedEdgesCache; + std::map meshedFacesCache; + std::map meshedEdgeRefCounts; + std::vector local_to_global_point_ids; + + // 清空当前 component 的增量网格缓存。 + void clear(); +}; diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index e347ddd..83fe809 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -65,6 +65,9 @@ std::any systems::algo::GmshMeshHandler::execute( } ComponentData& comp = context.cur_component.component(); + ModelLayer& modelLayer = context.cur_component.manager(); + removeExpiredStates(modelLayer); + GeometryData* geometry = comp.geometry.get(); if (!geometry || !geometry->rootShape) { spdlog::error("GmshMesh: current component {} has no geometry", @@ -81,16 +84,18 @@ std::any systems::algo::GmshMeshHandler::execute( meshData = comp.mesh.get(); } + GmshIncrementalMeshState& state = component_states_[context.cur_component.componentId()]; + // 首次执行时建立 OCC 面/边索引,后续单面划分复用该上下文。 - if (!geometry->gmsh_mesh_state.meshContext) { + if (!state.meshContext) { spdlog::info("GmshMesh: init occId..."); - geometry->gmsh_mesh_state.meshContext = std::make_unique(*geometry->rootShape); + state.meshContext = std::make_unique(*geometry->rootShape); spdlog::info("GmshMesh: {} face, {} global edge", - geometry->gmsh_mesh_state.meshContext->faceCount(), - geometry->gmsh_mesh_state.meshContext->globalEdgeCount()); + state.meshContext->faceCount(), + state.meshContext->globalEdgeCount()); } - std::size_t totalFaces = geometry->gmsh_mesh_state.meshContext->faceCount(); + std::size_t totalFaces = state.meshContext->faceCount(); if (static_cast(faceIndex) >= totalFaces) { spdlog::error("GmshMesh: face id {} out of range (total {} face)", faceIndex, totalFaces); @@ -101,20 +106,19 @@ std::any systems::algo::GmshMeshHandler::execute( meshSize = IncrementalMeshTools::estimateMeshSize(*geometry); SingleFaceMeshResult result; - ModelLayer& modelLayer = context.cur_component.manager(); if (operationMode == 2) { spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); - if (IncrementalMeshTools::deleteFaceMesh(*meshData, *geometry, modelLayer, static_cast(faceIndex))) + if (IncrementalMeshTools::deleteFaceMesh(*meshData, state, modelLayer, static_cast(faceIndex))) spdlog::info("GmshMesh: delete face {} success", faceIndex); } else if (operationMode == 3) { spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, modelLayer, static_cast(faceIndex), meshSize); + *meshData, *geometry, state, modelLayer, static_cast(faceIndex), meshSize); } else { spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, modelLayer, static_cast(faceIndex), meshSize); + *meshData, *geometry, state, modelLayer, static_cast(faceIndex), meshSize); } if (operationMode != 2) { @@ -127,18 +131,18 @@ std::any systems::algo::GmshMeshHandler::execute( faceIndex, result.vertices.size(), result.face_vertices_offset.size() - 1, - geometry->gmsh_mesh_state.meshedEdgeRefCounts.size()); - - std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { - spdlog::error("GmshMesh: cant save single face"); - return {}; - } - context.io_system.read(faceOut, "Wavefront .obj file", {}); + state.meshedEdgeRefCounts.size()); + + //std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; + //if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { + // spdlog::error("GmshMesh: cant save single face"); + // return {}; + //} + //context.io_system.read(faceOut, "Wavefront .obj file", {}); } std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeMeshObj(*meshData, *geometry, meshOut)) { + if (!IncrementalMeshTools::writeMeshObj(*meshData, state, meshOut)) { spdlog::error("GmshMesh: cant save meshdata"); return {}; } @@ -148,6 +152,17 @@ std::any systems::algo::GmshMeshHandler::execute( return {}; } +void systems::algo::GmshMeshHandler::removeExpiredStates(const ModelLayer& model_layer) +{ + for (auto it = component_states_.begin(); it != component_states_.end();) { + if (!model_layer.findComponent(it->first)) { + it = component_states_.erase(it); + } else { + ++it; + } + } +} + std::vector systems::algo::GmshMeshHandler::args_type() const { return { diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.h b/plugins/algo/GmshPlugin/GmshMeshHandler.h index 17355d2..61cfde1 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.h +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.h @@ -1,8 +1,12 @@ #pragma once #include "AlgorithmHandler.h" +#include "GmshIncrementalMeshState.h" #include +#include #include +class ModelLayer; + namespace systems::algo { class GmshMeshHandler : public AlgorithmHandler { public: @@ -11,5 +15,11 @@ public: std::any execute(HandlerContext& context, const std::vector& args) override; std::vector args_type() const override; + +private: + // 删除已经不存在的 component 对应缓存,避免插件长期运行时残留无效状态。 + void removeExpiredStates(const ModelLayer& model_layer); + + std::unordered_map component_states_; }; -} // namespace systems::algo \ No newline at end of file +} // namespace systems::algo diff --git a/model/data/IncrementalMeshContext.cpp b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp similarity index 100% rename from model/data/IncrementalMeshContext.cpp rename to plugins/algo/GmshPlugin/IncrementalMeshContext.cpp diff --git a/model/data/IncrementalMeshContext.h b/plugins/algo/GmshPlugin/IncrementalMeshContext.h similarity index 100% rename from model/data/IncrementalMeshContext.h rename to plugins/algo/GmshPlugin/IncrementalMeshContext.h diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 2f369df..d85784f 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -39,16 +39,16 @@ class TempNodeLookup { public: explicit TempNodeLookup( MeshData& mesh_data, - GeometryData& geometry, + GmshIncrementalMeshState& state, ModelLayer& model_layer, double tolerance = 1e-7) : _mesh_data(mesh_data) - , _geometry(geometry) + , _state(state) , _model_layer(model_layer) , _tolerance(tolerance) { const auto& vertices = _mesh_data.vertex_positions_; - const auto& global_ids = _geometry.gmsh_mesh_state.local_to_global_point_ids; + const auto& global_ids = _state.local_to_global_point_ids; for (size_t i = 0; i < vertices.size(); ++i) { auto qc = _quantize(vertices[i][0], vertices[i][1], vertices[i][2]); if (i < global_ids.size()) { @@ -72,7 +72,7 @@ public: _mesh_data.vertex_positions_.push_back(point); _mesh_data.vertex_count_ = static_cast(_mesh_data.vertex_positions_.size()); _mesh_data.point_ids_are_global_ = true; - _geometry.gmsh_mesh_state.local_to_global_point_ids.push_back(global_id); + _state.local_to_global_point_ids.push_back(global_id); _map[qc] = global_id; return global_id; } @@ -107,7 +107,7 @@ private: } MeshData& _mesh_data; - GeometryData& _geometry; + GmshIncrementalMeshState& _state; ModelLayer& _model_layer; double _tolerance; std::unordered_map _map; @@ -663,9 +663,8 @@ SingleFaceMeshResult extractFaceMesh(int faceTag) } // ---- 存储新边 ---- -void storeNewEdges(GeometryData& geometry, const std::map& g2o) +void storeNewEdges(GmshIncrementalMeshState& state, const std::map& g2o) { - auto& state = geometry.gmsh_mesh_state; int nNew = 0; int nShared = 0; for (auto& [gt, oid] : g2o) { @@ -684,7 +683,11 @@ void storeNewEdges(GeometryData& geometry, const std::map& g2o) spdlog::info("GmshMesh: {} new edges stored, {} edges reused (total cached: {})", nNew, nShared, state.meshedEdgeRefCounts.size()); } -void mergeMeshResult(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, const SingleFaceMeshResult& result) +void mergeMeshResult( + MeshData& mesh_data, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + const SingleFaceMeshResult& result) { if (!result.success) return; @@ -692,7 +695,7 @@ void mergeMeshResult(MeshData& mesh_data, GeometryData& geometry, ModelLayer& mo if (mesh_data.face_vertices_offset_.empty()) mesh_data.face_vertices_offset_.push_back(0); - TempNodeLookup lookup(mesh_data, geometry, model_layer, 1e-7); + TempNodeLookup lookup(mesh_data, state, model_layer, 1e-7); std::vector localToGlobal(result.vertices.size()); for (size_t i = 0; i < result.vertices.size(); ++i) { @@ -722,26 +725,31 @@ void mergeMeshResult(MeshData& mesh_data, GeometryData& geometry, ModelLayer& mo // 公开接口 bool IncrementalMeshTools::initMeshing(const std::string& stepFile, - GeometryData& geometry) + GeometryData& geometry, + GmshIncrementalMeshState& state) { - geometry.gmsh_mesh_state.clear(); + state.clear(); geometry.rootShape = std::make_unique(loadStep(stepFile)); if (geometry.rootShape->IsNull()) { spdlog::error("Failed to load: {}", stepFile); return false; } - geometry.gmsh_mesh_state.meshContext = std::make_unique(*geometry.rootShape); + state.meshContext = std::make_unique(*geometry.rootShape); spdlog::info("Loaded: {} faces, {} global edges", - geometry.gmsh_mesh_state.meshContext->faceCount(), - geometry.gmsh_mesh_state.meshContext->globalEdgeCount()); - return geometry.gmsh_mesh_state.meshContext->faceCount() > 0; + state.meshContext->faceCount(), + state.meshContext->globalEdgeCount()); + return state.meshContext->faceCount() > 0; } SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( - MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex, double meshSize) + MeshData& mesh_data, + GeometryData& geometry, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + std::size_t faceIndex, + double meshSize) { SingleFaceMeshResult result; - auto& state = geometry.gmsh_mesh_state; if (!state.meshContext) { spdlog::error("meshContext null, call initMeshing first"); @@ -807,7 +815,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::model::mesh::generate(2); } catch (const std::exception& e) { spdlog::error(" Mesh failed: {}", e.what()); - storeNewEdges(geometry, gmshToOcc); + storeNewEdges(state, gmshToOcc); gmsh::finalize(); return result; } @@ -825,18 +833,18 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } if (!has) { spdlog::warn(" No surface elements"); - storeNewEdges(geometry, gmshToOcc); + storeNewEdges(state, gmshToOcc); gmsh::finalize(); return result; } } result = extractFaceMesh(faceTag); - storeNewEdges(geometry, gmshToOcc); + storeNewEdges(state, gmshToOcc); state.meshedFacesCache[faceIndex] = result; // 缓存面结果 if (result.success) { - mergeMeshResult(mesh_data, geometry, model_layer, result); + mergeMeshResult(mesh_data, state, model_layer, result); } gmsh::finalize(); return result; @@ -860,14 +868,14 @@ double IncrementalMeshTools::estimateMeshSize(const GeometryData& geometry) return 10.0; } -std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) +std::size_t IncrementalMeshTools::faceCount(const GmshIncrementalMeshState& state) { - return geometry.gmsh_mesh_state.meshContext ? geometry.gmsh_mesh_state.meshContext->faceCount() : 0; + return state.meshContext ? state.meshContext->faceCount() : 0; } -std::size_t IncrementalMeshTools::meshedEdgeCount(const GeometryData& geometry) +std::size_t IncrementalMeshTools::meshedEdgeCount(const GmshIncrementalMeshState& state) { - return geometry.gmsh_mesh_state.meshedEdgeRefCounts.size(); + return state.meshedEdgeRefCounts.size(); } bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath) @@ -893,7 +901,10 @@ bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, c return true; } -bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const GeometryData& geometry, const std::filesystem::path& filepath) +bool IncrementalMeshTools::writeMeshObj( + const MeshData& res, + const GmshIncrementalMeshState& state, + const std::filesystem::path& filepath) { std::ofstream ofs(filepath); if (!ofs.is_open()) @@ -906,7 +917,7 @@ bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const GeometryData& ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; std::unordered_map globalToLocal; - const auto& globalIds = geometry.gmsh_mesh_state.local_to_global_point_ids; + const auto& globalIds = state.local_to_global_point_ids; for (std::size_t i = 0; i < globalIds.size(); ++i) { globalToLocal[globalIds[i]] = i + 1; } @@ -928,9 +939,12 @@ bool IncrementalMeshTools::writeMeshObj(const MeshData& res, const GeometryData& return true; } -bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex) +bool IncrementalMeshTools::deleteFaceMesh( + MeshData& mesh_data, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + std::size_t faceIndex) { - auto& state = geometry.gmsh_mesh_state; // 检查是否存在该面的缓存 auto it = state.meshedFacesCache.find(faceIndex); if (it == state.meshedFacesCache.end()) { @@ -962,7 +976,7 @@ bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, GeometryData& geo mesh_data.face_vertices_offset_.clear(); mesh_data.face_vertices_offset_.push_back(0); - TempNodeLookup lookup(mesh_data, geometry, model_layer, 1e-7); + TempNodeLookup lookup(mesh_data, state, model_layer, 1e-7); // 遍历目前还保留的所有有效面,按序装入MeshData for (const auto& [fIdx, faceMeshResult] : state.meshedFacesCache) { @@ -995,11 +1009,16 @@ bool IncrementalMeshTools::deleteFaceMesh(MeshData& mesh_data, GeometryData& geo } SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( - MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex, double meshSize) + MeshData& mesh_data, + GeometryData& geometry, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + std::size_t faceIndex, + double meshSize) { - if (geometry.gmsh_mesh_state.meshedFacesCache.find(faceIndex) != geometry.gmsh_mesh_state.meshedFacesCache.end()) { + if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) { spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); - deleteFaceMesh(mesh_data, geometry, model_layer, faceIndex); + deleteFaceMesh(mesh_data, state, model_layer, faceIndex); } - return meshSingleFace(mesh_data, geometry, model_layer, faceIndex, meshSize); + return meshSingleFace(mesh_data, geometry, state, model_layer, faceIndex, meshSize); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index a304420..b1878f0 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -4,6 +4,7 @@ #include #include #include +#include "GmshIncrementalMeshState.h" #include "GeometryData.h" struct MeshData; @@ -11,11 +12,12 @@ class ModelLayer; namespace IncrementalMeshTools { -bool initMeshing(const std::string& stepFile, GeometryData& geometry); +bool initMeshing(const std::string& stepFile, GeometryData& geometry, GmshIncrementalMeshState& state); SingleFaceMeshResult meshSingleFace( MeshData& mesh_data, GeometryData& geometry, + GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, double meshSize); @@ -23,20 +25,25 @@ SingleFaceMeshResult meshSingleFace( SingleFaceMeshResult remeshSingleFace( MeshData& mesh_data, GeometryData& geometry, + GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, double meshSize); -bool deleteFaceMesh(MeshData& mesh_data, GeometryData& geometry, ModelLayer& model_layer, std::size_t faceIndex); +bool deleteFaceMesh( + MeshData& mesh_data, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + std::size_t faceIndex); double estimateMeshSize(const GeometryData& geometry); -std::size_t faceCount(const GeometryData& geometry); +std::size_t faceCount(const GmshIncrementalMeshState& state); -std::size_t meshedEdgeCount(const GeometryData& geometry); +std::size_t meshedEdgeCount(const GmshIncrementalMeshState& state); bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); -bool writeMeshObj(const MeshData& res, const GeometryData& geometry, const std::filesystem::path& filepath); +bool writeMeshObj(const MeshData& res, const GmshIncrementalMeshState& state, const std::filesystem::path& filepath); } // namespace IncrementalMeshTools diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index 69aca38..12c6b3c 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -5,7 +5,6 @@ #include "ArgObject.h" #include "ComponentData.h" #include "GeometryData.h" -#include "IncrementalMeshContext.h" #include "ModelData.h" #include "ModelIOSystemBase.h" #include "ModelLayer.h" @@ -74,9 +73,7 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") REQUIRE(comp != nullptr); REQUIRE(comp->mesh != nullptr); REQUIRE(comp->geometry != nullptr); - REQUIRE(comp->geometry->gmsh_mesh_state.meshContext != nullptr); - REQUIRE(comp->geometry->gmsh_mesh_state.meshContext->faceCount() == 6); - REQUIRE(comp->geometry->gmsh_mesh_state.meshedEdgeRefCounts.size() == 4); - REQUIRE(comp->geometry->gmsh_mesh_state.meshedEdgesCache.size() == 4); - REQUIRE_FALSE(comp->geometry->gmsh_mesh_state.local_to_global_point_ids.empty()); + REQUIRE_FALSE(comp->mesh->vertex_positions_.empty()); + REQUIRE_FALSE(comp->mesh->face_vertices_.empty()); + REQUIRE(comp->mesh->face_vertices_offset_.size() > 1); } diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index 09ded56..b0d0b18 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -30,6 +30,7 @@ // Runtime state shared by the VTK key callback and the example main loop. struct AppContext { GeometryData geometry; + GmshIncrementalMeshState gmshState; MeshData meshData; ModelLayer modelLayer; vtkSmartPointer polyData; @@ -39,10 +40,10 @@ struct AppContext { }; // Builds a local file/render index for mesh vertices whose faces store global point ids. -static std::unordered_map buildGlobalToLocalPointMap(const GeometryData& geometry) +static std::unordered_map buildGlobalToLocalPointMap(const GmshIncrementalMeshState& state) { std::unordered_map globalToLocal; - const auto& globalIds = geometry.gmsh_mesh_state.local_to_global_point_ids; + const auto& globalIds = state.local_to_global_point_ids; for (std::size_t i = 0; i < globalIds.size(); ++i) { globalToLocal[globalIds[i]] = i; } @@ -58,7 +59,7 @@ static void reloadPolyData(AppContext& ctx) } auto polys = vtkSmartPointer::New(); - auto globalToLocal = buildGlobalToLocalPointMap(ctx.geometry); + auto globalToLocal = buildGlobalToLocalPointMap(ctx.gmshState); for (std::size_t i = 0; i + 1 < ctx.meshData.face_vertices_offset_.size(); ++i) { std::size_t begin = ctx.meshData.face_vertices_offset_[i]; @@ -89,9 +90,9 @@ static void reloadPolyData(AppContext& ctx) static void resetGeneratedMesh(AppContext& ctx) { ctx.meshData.init(); - ctx.geometry.gmsh_mesh_state.clear(); + ctx.gmshState.clear(); if (ctx.geometry.rootShape) { - ctx.geometry.gmsh_mesh_state.meshContext = + ctx.gmshState.meshContext = std::make_unique(*ctx.geometry.rootShape); } ctx.currentIndex = 0; @@ -100,7 +101,7 @@ static void resetGeneratedMesh(AppContext& ctx) } // Saves the example mesh by translating global point ids back to local file node ids. -static void saveMesh(const MeshData& mesh, const GeometryData& geometry, const std::string& filename) +static void saveMesh(const MeshData& mesh, const GmshIncrementalMeshState& state, const std::string& filename) { if (mesh.vertex_positions_.empty()) { spdlog::warn("No mesh data to save."); @@ -114,7 +115,7 @@ static void saveMesh(const MeshData& mesh, const GeometryData& geometry, const s std::vector nodeTags(mesh.vertex_positions_.size()); std::vector nodeCoords(mesh.vertex_positions_.size() * 3); - auto globalToLocal = buildGlobalToLocalPointMap(geometry); + auto globalToLocal = buildGlobalToLocalPointMap(state); for (std::size_t i = 0; i < mesh.vertex_positions_.size(); ++i) { nodeTags[i] = i + 1; @@ -168,7 +169,7 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, auto* interactor = static_cast(caller); std::string key = interactor->GetKeySym(); - std::size_t total = IncrementalMeshTools::faceCount(ctx->geometry); + std::size_t total = IncrementalMeshTools::faceCount(ctx->gmshState); if (key == "space") { if (ctx->currentIndex >= total) { @@ -177,7 +178,7 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, } auto result = IncrementalMeshTools::meshSingleFace( - ctx->meshData, ctx->geometry, ctx->modelLayer, ctx->currentIndex, ctx->meshSize); + ctx->meshData, ctx->geometry, ctx->gmshState, ctx->modelLayer, ctx->currentIndex, ctx->meshSize); ctx->currentIndex++; if (result.success) { @@ -186,11 +187,11 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, ctx->meshSize *= 1.5; spdlog::info("nodes={}, cached_edges={}, next_size={:.4f}", ctx->meshData.vertex_positions_.size(), - IncrementalMeshTools::meshedEdgeCount(ctx->geometry), + IncrementalMeshTools::meshedEdgeCount(ctx->gmshState), ctx->meshSize); } } else if (key == "s" || key == "S") { - saveMesh(ctx->meshData, ctx->geometry, "final_mesh.msh"); + saveMesh(ctx->meshData, ctx->gmshState, "final_mesh.msh"); } else if (key == "r" || key == "R") { resetGeneratedMesh(*ctx); spdlog::info("Reset mesh, size={:.4f}", ctx->meshSize); @@ -206,7 +207,7 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, return; } ctx->currentIndex--; - if (IncrementalMeshTools::deleteFaceMesh(ctx->meshData, ctx->geometry, ctx->modelLayer, ctx->currentIndex)) { + if (IncrementalMeshTools::deleteFaceMesh(ctx->meshData, ctx->gmshState, ctx->modelLayer, ctx->currentIndex)) { reloadPolyData(*ctx); } } else if (key == "h" || key == "H") { @@ -225,7 +226,7 @@ int main(int argc, char* argv[]) ctx.polyData = vtkSmartPointer::New(); ctx.meshData.init(); - if (!IncrementalMeshTools::initMeshing(path, ctx.geometry)) { + if (!IncrementalMeshTools::initMeshing(path, ctx.geometry, ctx.gmshState)) { spdlog::error("Cannot import: {}", path); return 1; } -- Gitee From de321a34fbb510412170feb813f2ce8af3a036e6 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 31 May 2026 16:03:06 +0800 Subject: [PATCH 04/21] =?UTF-8?q?refactor(gmsh):=20=E5=A4=8D=E7=94=A8CAD?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E7=AE=A1=E7=90=86Gmsh=E5=A2=9E=E9=87=8F?= =?UTF-8?q?=E7=BD=91=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GmshPlugin/GmshIncrementalMeshState.h | 6 +- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 2 +- .../GmshPlugin/IncrementalMeshContext.cpp | 62 ++++++++++++------- .../algo/GmshPlugin/IncrementalMeshContext.h | 25 ++++---- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 35 ++++++----- .../algo/GmshPlugin/IncrementalMeshTools.h | 7 ++- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 5 +- 7 files changed, 87 insertions(+), 55 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h index d762f59..70eae99 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -32,9 +32,11 @@ struct GmshIncrementalMeshState { ~GmshIncrementalMeshState(); std::unique_ptr meshContext; - std::map meshedEdgesCache; + // 已划分 CAD 边的节点缓存,key 使用 pr-38 的全局 CAD 边 ID。 + std::map meshedEdgesCache; std::map meshedFacesCache; - std::map meshedEdgeRefCounts; + // 已划分 CAD 边被多少个面复用,用于删除面网格时判断是否清理边缓存。 + std::map meshedEdgeRefCounts; std::vector local_to_global_point_ids; // 清空当前 component 的增量网格缓存。 diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 83fe809..dc83f96 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -89,7 +89,7 @@ std::any systems::algo::GmshMeshHandler::execute( // 首次执行时建立 OCC 面/边索引,后续单面划分复用该上下文。 if (!state.meshContext) { spdlog::info("GmshMesh: init occId..."); - state.meshContext = std::make_unique(*geometry->rootShape); + state.meshContext = std::make_unique(*geometry, modelLayer.geomRegistry()); spdlog::info("GmshMesh: {} face, {} global edge", state.meshContext->faceCount(), state.meshContext->globalEdgeCount()); diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp index dda2688..8dba728 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp @@ -1,9 +1,11 @@ #include "IncrementalMeshContext.h" +#include "GeometryData.h" +#include "GeometryRegistry.h" +#include "GeometrySubshapeIndex.h" + #include -#include #include -#include #include #include #include @@ -13,53 +15,67 @@ #include #include -struct IncrementalMeshContext::Impl { - TopTools_IndexedMapOfShape globalEdgeMap; - TopTools_IndexedMapOfShape globalFaceMap; -}; +namespace { -IncrementalMeshContext::IncrementalMeshContext(const TopoDS_Shape& shape) - : impl_(std::make_unique()) +int edgeTypeIndex() { - TopExp::MapShapes(shape, TopAbs_EDGE, impl_->globalEdgeMap); - TopExp::MapShapes(shape, TopAbs_FACE, impl_->globalFaceMap); + return GeometrySubshapeIndex::typeIndex(TopAbs_EDGE); +} + +int faceTypeIndex() +{ + return GeometrySubshapeIndex::typeIndex(TopAbs_FACE); +} + +} // namespace + +IncrementalMeshContext::IncrementalMeshContext(GeometryData& geometry, GeometryRegistry& registry) + : cad_index_(&geometry.cad_index) + , registry_(®istry) +{ + geometry.ensureCadIndexBuilt(registry); } IncrementalMeshContext::~IncrementalMeshContext() = default; int IncrementalMeshContext::globalEdgeCount() const { - return impl_->globalEdgeMap.Extent(); + return cad_index_->type_maps[edgeTypeIndex()].Extent(); } -std::vector IncrementalMeshContext::getFaceEdgeIds(const TopoDS_Face& face) const +std::vector IncrementalMeshContext::getFaceEdgeIds(const TopoDS_Face& face) const { - std::vector ids; - std::set seen; + std::vector ids; + std::set seen; + const auto& edgeMap = cad_index_->type_maps[edgeTypeIndex()]; + for (TopExp_Explorer ex(face, TopAbs_EDGE); ex.More(); ex.Next()) { - int gid = impl_->globalEdgeMap.FindIndex(ex.Current()); - if (gid > 0 && seen.insert(gid).second) + int localEdgeId = edgeMap.FindIndex(ex.Current()); + GeomEdgeId gid = cad_index_->edgeGlobalId(localEdgeId); + if (gid != kInvalidGeomEdgeId && seen.insert(gid).second) ids.push_back(gid); } return ids; } -TopoDS_Edge IncrementalMeshContext::getEdgeByGlobalId(int globalId) const +TopoDS_Edge IncrementalMeshContext::getEdgeByGlobalId(GeomEdgeId globalId) const { - if (globalId < 1 || globalId > impl_->globalEdgeMap.Extent()) - throw std::out_of_range("invalid edge ID " + std::to_string(globalId)); - return TopoDS::Edge(impl_->globalEdgeMap(globalId)); + const TopoDS_Shape* shape = registry_->getEdge(globalId); + if (!shape) + throw std::out_of_range("invalid CAD edge ID " + std::to_string(globalId)); + return TopoDS::Edge(*shape); } std::size_t IncrementalMeshContext::faceCount() const { - return static_cast(impl_->globalFaceMap.Extent()); + return static_cast(cad_index_->type_maps[faceTypeIndex()].Extent()); } TopoDS_Face IncrementalMeshContext::getFaceByIndex(std::size_t index) const { int occIndex = static_cast(index) + 1; - if (occIndex < 1 || occIndex > impl_->globalFaceMap.Extent()) + const auto& faceMap = cad_index_->type_maps[faceTypeIndex()]; + if (occIndex < 1 || occIndex > faceMap.Extent()) throw std::out_of_range("face index " + std::to_string(index) + " out of range"); - return TopoDS::Face(impl_->globalFaceMap(occIndex)); + return TopoDS::Face(faceMap(occIndex)); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.h b/plugins/algo/GmshPlugin/IncrementalMeshContext.h index d725a7d..018d62d 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshContext.h @@ -1,27 +1,30 @@ #pragma once -#include +#include "Core.h" + #include -class TopoDS_Shape; class TopoDS_Face; class TopoDS_Edge; +struct GeometryData; +struct GeometrySubshapeIndex; +class GeometryRegistry; -// 为增量网格划分缓存 OCC 拓扑索引,避免每次划分单个面时重复遍历整棵 Shape。 +// 将 GeometryRegistry 的 CAD 子形状索引适配给 Gmsh 增量网格流程使用。 class IncrementalMeshContext { public: - explicit IncrementalMeshContext(const TopoDS_Shape& shape); + IncrementalMeshContext(GeometryData& geometry, GeometryRegistry& registry); ~IncrementalMeshContext(); IncrementalMeshContext(const IncrementalMeshContext&) = delete; IncrementalMeshContext& operator=(const IncrementalMeshContext&) = delete; - // 返回当前 Shape 中注册到全局 OCC 边索引的边数量。 + // 返回当前 CAD component 中注册到 GeometryRegistry 的边数量。 int globalEdgeCount() const; - // 返回指定面包含的全局 OCC 边 ID,供 Gmsh 边界复用逻辑使用。 - std::vector getFaceEdgeIds(const TopoDS_Face& face) const; - // 根据全局 OCC 边 ID 取回原始 TopoDS_Edge。 - TopoDS_Edge getEdgeByGlobalId(int globalId) const; + // 返回指定面包含的全局 CAD 边 ID,供 Gmsh 边界复用逻辑使用。 + std::vector getFaceEdgeIds(const TopoDS_Face& face) const; + // 根据全局 CAD 边 ID 取回原始 TopoDS_Edge。 + TopoDS_Edge getEdgeByGlobalId(GeomEdgeId globalId) const; // 返回当前 Shape 中可独立划分的面数量。 std::size_t faceCount() const; @@ -29,6 +32,6 @@ public: TopoDS_Face getFaceByIndex(std::size_t index) const; private: - struct Impl; - std::unique_ptr impl_; + GeometrySubshapeIndex* cad_index_ {}; + GeometryRegistry* registry_ {}; }; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index d85784f..60e6f22 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -1,6 +1,7 @@ #include "IncrementalMeshTools.h" #include "IncrementalMeshContext.h" +#include "GeometryRegistry.h" #include "MeshData.h" #include "ModelLayer.h" @@ -271,7 +272,7 @@ std::vector sampleGmshEdgePoints(int gmshTag, int sampleCount = 3) } // ---- OCC 边特征 ---- -EdgeGeoFeature computeOCCEdgeFeature(int gid, const IncrementalMeshContext& ctx) +EdgeGeoFeature computeOCCEdgeFeature(GeomEdgeId gid, const IncrementalMeshContext& ctx) { TopoDS_Edge edge = ctx.getEdgeByGlobalId(gid); @@ -374,13 +375,13 @@ EdgeGeoFeature computeGmshEdgeFeature(int gmshTag) } // ---- 匹配 ---- -std::map matchGmshToOCCEdges(const TopoDS_Face& face, +std::map matchGmshToOCCEdges(const TopoDS_Face& face, const IncrementalMeshContext& ctx) { // OCC 当前 face 的边 auto occIds = ctx.getFaceEdgeIds(face); - std::vector> occFeats; - for (int gid : occIds) { + std::vector> occFeats; + for (GeomEdgeId gid : occIds) { occFeats.push_back({ gid, computeOCCEdgeFeature(gid, ctx) }); } @@ -409,14 +410,14 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, double scale = getShapeScale(face); double acceptTol = 5e-2; // 归一化后的阈值 - std::map result; - std::set matched; + std::map result; + std::set matched; for (int gmshTag : gmshEdges) { auto gf = computeGmshEdgeFeature(gmshTag); double best = 1e30; - int bestId = -1; + GeomEdgeId bestId = kInvalidGeomEdgeId; for (auto& [id, of] : occFeats) { if (matched.count(id)) @@ -440,7 +441,7 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, } } - if (bestId > 0 && best < acceptTol) { + if (bestId != kInvalidGeomEdgeId && best < acceptTol) { result[gmshTag] = bestId; matched.insert(bestId); spdlog::info(" GMSH edge {} -> OCC edge {} (score={:.3e})", @@ -456,7 +457,7 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, } // ---- 注入约束边 ---- -bool injectConstrainedEdge(int gmshTag, int occId, +bool injectConstrainedEdge(int gmshTag, const MeshedEdgeData& nodes, std::size_t& nodeCounter, std::size_t& elemCounter, @@ -662,8 +663,8 @@ SingleFaceMeshResult extractFaceMesh(int faceTag) return result; } -// ---- 存储新边 ---- -void storeNewEdges(GmshIncrementalMeshState& state, const std::map& g2o) +// 将当前划分出的 Gmsh 边节点按全局 CAD 边 ID 缓存,供后续相邻面复用。 +void storeNewEdges(GmshIncrementalMeshState& state, const std::map& g2o) { int nNew = 0; int nShared = 0; @@ -726,15 +727,19 @@ void mergeMeshResult( // 公开接口 bool IncrementalMeshTools::initMeshing(const std::string& stepFile, GeometryData& geometry, - GmshIncrementalMeshState& state) + GmshIncrementalMeshState& state, + GeometryRegistry& registry) { state.clear(); + if (geometry.cad_index.built) + geometry.cad_index.release(registry); + geometry.rootShape = std::make_unique(loadStep(stepFile)); if (geometry.rootShape->IsNull()) { spdlog::error("Failed to load: {}", stepFile); return false; } - state.meshContext = std::make_unique(*geometry.rootShape); + state.meshContext = std::make_unique(geometry, registry); spdlog::info("Loaded: {} faces, {} global edges", state.meshContext->faceCount(), state.meshContext->globalEdgeCount()); @@ -796,7 +801,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( for (auto& [gt, oid] : gmshToOcc) { if (state.meshedEdgeRefCounts.count(oid) > 0) { - injectConstrainedEdge(gt, oid, state.meshedEdgesCache.at(oid), + injectConstrainedEdge(gt, state.meshedEdgesCache.at(oid), nodeCounter, elemCounter, vtxNodeMap); shared++; } else { @@ -956,7 +961,7 @@ bool IncrementalMeshTools::deleteFaceMesh( if (state.meshContext) { TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); auto occEdges = state.meshContext->getFaceEdgeIds(face); - for (int globalEdgeId : occEdges) { + for (GeomEdgeId globalEdgeId : occEdges) { auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); if (refIt != state.meshedEdgeRefCounts.end()) { refIt->second--; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index b1878f0..ce2aff1 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -9,10 +9,15 @@ struct MeshData; class ModelLayer; +class GeometryRegistry; namespace IncrementalMeshTools { -bool initMeshing(const std::string& stepFile, GeometryData& geometry, GmshIncrementalMeshState& state); +bool initMeshing( + const std::string& stepFile, + GeometryData& geometry, + GmshIncrementalMeshState& state, + GeometryRegistry& registry); SingleFaceMeshResult meshSingleFace( MeshData& mesh_data, diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index b0d0b18..cdc3e7f 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -92,8 +92,9 @@ static void resetGeneratedMesh(AppContext& ctx) ctx.meshData.init(); ctx.gmshState.clear(); if (ctx.geometry.rootShape) { + ctx.geometry.ensureCadIndexBuilt(ctx.modelLayer.geomRegistry()); ctx.gmshState.meshContext = - std::make_unique(*ctx.geometry.rootShape); + std::make_unique(ctx.geometry, ctx.modelLayer.geomRegistry()); } ctx.currentIndex = 0; ctx.meshSize = IncrementalMeshTools::estimateMeshSize(ctx.geometry); @@ -226,7 +227,7 @@ int main(int argc, char* argv[]) ctx.polyData = vtkSmartPointer::New(); ctx.meshData.init(); - if (!IncrementalMeshTools::initMeshing(path, ctx.geometry, ctx.gmshState)) { + if (!IncrementalMeshTools::initMeshing(path, ctx.geometry, ctx.gmshState, ctx.modelLayer.geomRegistry())) { spdlog::error("Cannot import: {}", path); return 1; } -- Gitee From d11180c364580367671a9c3c0ed1f2e28a3b7ad5 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Wed, 3 Jun 2026 13:28:19 +0800 Subject: [PATCH 05/21] =?UTF-8?q?refeactor=EF=BC=88gmsh=EF=BC=89:=E7=BC=93?= =?UTF-8?q?=E5=AD=98=20CAD=20=E9=9D=A2=E8=BE=B9=E6=8B=93=E6=89=91=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=20Gmsh=20=E8=BE=B9=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GmshPlugin/IncrementalMeshContext.cpp | 36 ++++++++++++++----- .../algo/GmshPlugin/IncrementalMeshContext.h | 9 +++++ .../algo/GmshPlugin/IncrementalMeshTools.cpp | 6 ++-- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp index 8dba728..dfec9b5 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp @@ -34,6 +34,23 @@ IncrementalMeshContext::IncrementalMeshContext(GeometryData& geometry, GeometryR , registry_(®istry) { geometry.ensureCadIndexBuilt(registry); + + // 计算每个 CAD face 的边集合,后续 Gmsh 单面匹配只查这个拓扑缓存。 + const auto& faceMap = cad_index_->type_maps[faceTypeIndex()]; + const auto& edgeMap = cad_index_->type_maps[edgeTypeIndex()]; + face_edge_infos_.resize(static_cast(faceMap.Extent())); + + for (int faceIdx = 1; faceIdx <= faceMap.Extent(); ++faceIdx) { + auto face = TopoDS::Face(faceMap(faceIdx)); + std::set seen; + + for (TopExp_Explorer ex(face, TopAbs_EDGE); ex.More(); ex.Next()) { + int localEdgeId = edgeMap.FindIndex(ex.Current()); + GeomEdgeId gid = cad_index_->edgeGlobalId(localEdgeId); + if (gid != kInvalidGeomEdgeId && seen.insert(gid).second) + face_edge_infos_[static_cast(faceIdx - 1)].push_back({ gid, localEdgeId }); + } + } } IncrementalMeshContext::~IncrementalMeshContext() = default; @@ -43,18 +60,19 @@ int IncrementalMeshContext::globalEdgeCount() const return cad_index_->type_maps[edgeTypeIndex()].Extent(); } +std::vector IncrementalMeshContext::getFaceEdgeInfos(const TopoDS_Face& face) const +{ + int localFaceId = cad_index_->type_maps[faceTypeIndex()].FindIndex(face); + if (localFaceId < 1 || static_cast(localFaceId) > face_edge_infos_.size()) + return {}; + return face_edge_infos_[static_cast(localFaceId - 1)]; +} + std::vector IncrementalMeshContext::getFaceEdgeIds(const TopoDS_Face& face) const { std::vector ids; - std::set seen; - const auto& edgeMap = cad_index_->type_maps[edgeTypeIndex()]; - - for (TopExp_Explorer ex(face, TopAbs_EDGE); ex.More(); ex.Next()) { - int localEdgeId = edgeMap.FindIndex(ex.Current()); - GeomEdgeId gid = cad_index_->edgeGlobalId(localEdgeId); - if (gid != kInvalidGeomEdgeId && seen.insert(gid).second) - ids.push_back(gid); - } + for (const auto& info : getFaceEdgeInfos(face)) + ids.push_back(info.edgeId); return ids; } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.h b/plugins/algo/GmshPlugin/IncrementalMeshContext.h index 018d62d..ace2abc 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshContext.h @@ -10,6 +10,12 @@ struct GeometryData; struct GeometrySubshapeIndex; class GeometryRegistry; +// 记录一个 CAD 面上的边拓扑关系,供 Gmsh 单面网格匹配时限定候选边集合。 +struct FaceEdgeInfo { + GeomEdgeId edgeId { kInvalidGeomEdgeId }; + int localEdgeId { 0 }; +}; + // 将 GeometryRegistry 的 CAD 子形状索引适配给 Gmsh 增量网格流程使用。 class IncrementalMeshContext { public: @@ -21,6 +27,8 @@ public: // 返回当前 CAD component 中注册到 GeometryRegistry 的边数量。 int globalEdgeCount() const; + // 返回指定面包含的边拓扑信息,候选范围只来自 CAD face 本身。 + std::vector getFaceEdgeInfos(const TopoDS_Face& face) const; // 返回指定面包含的全局 CAD 边 ID,供 Gmsh 边界复用逻辑使用。 std::vector getFaceEdgeIds(const TopoDS_Face& face) const; // 根据全局 CAD 边 ID 取回原始 TopoDS_Edge。 @@ -34,4 +42,5 @@ public: private: GeometrySubshapeIndex* cad_index_ {}; GeometryRegistry* registry_ {}; + std::vector> face_edge_infos_; }; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 60e6f22..dcfc8ce 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -379,10 +379,10 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, const IncrementalMeshContext& ctx) { // OCC 当前 face 的边 - auto occIds = ctx.getFaceEdgeIds(face); + auto edgeInfos = ctx.getFaceEdgeInfos(face); std::vector> occFeats; - for (GeomEdgeId gid : occIds) { - occFeats.push_back({ gid, computeOCCEdgeFeature(gid, ctx) }); + for (const auto& info : edgeInfos) { + occFeats.push_back({ info.edgeId, computeOCCEdgeFeature(info.edgeId, ctx) }); } // 只取当前 Gmsh face 的边界边,而不是整个模型所有 edge -- Gitee From 5aeacaac0961da7a21929d00480bb957fbe03ce2 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Wed, 3 Jun 2026 13:42:47 +0800 Subject: [PATCH 06/21] =?UTF-8?q?refeactor(gmsh):=E5=9C=A8operationMode?= =?UTF-8?q?=E4=B8=BA1=E6=97=B6=E5=A6=82=E6=9E=9C=E5=B7=B2=E5=88=92?= =?UTF-8?q?=E5=88=86=E5=88=99=E8=B7=B3=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 32 +++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index dc83f96..4d9063f 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -105,20 +105,28 @@ std::any systems::algo::GmshMeshHandler::execute( if (meshSize <= 0.0) meshSize = IncrementalMeshTools::estimateMeshSize(*geometry); + std::size_t faceKey = static_cast(faceIndex); SingleFaceMeshResult result; - if (operationMode == 2) { + if (operationMode == 1) { + if (state.meshedFacesCache.find(faceKey) != state.meshedFacesCache.end()) { + spdlog::info("GmshMesh: face {} already meshed, skip mesh mode; use remesh mode to rebuild", faceIndex); + return {}; + } + spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); + result = IncrementalMeshTools::meshSingleFace( + *meshData, *geometry, state, modelLayer, faceKey, meshSize); + } else if (operationMode == 2) { spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); - if (IncrementalMeshTools::deleteFaceMesh(*meshData, state, modelLayer, static_cast(faceIndex))) + if (IncrementalMeshTools::deleteFaceMesh(*meshData, state, modelLayer, faceKey)) spdlog::info("GmshMesh: delete face {} success", faceIndex); } else if (operationMode == 3) { spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, state, modelLayer, static_cast(faceIndex), meshSize); + *meshData, *geometry, state, modelLayer, faceKey, meshSize); } else { - spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); - result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, state, modelLayer, static_cast(faceIndex), meshSize); + spdlog::warn("GmshMesh: unknown operation mode {}, skip", operationMode); + return {}; } if (operationMode != 2) { @@ -133,12 +141,12 @@ std::any systems::algo::GmshMeshHandler::execute( result.face_vertices_offset.size() - 1, state.meshedEdgeRefCounts.size()); - //std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; - //if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { - // spdlog::error("GmshMesh: cant save single face"); - // return {}; - //} - //context.io_system.read(faceOut, "Wavefront .obj file", {}); + // std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; + // if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { + // spdlog::error("GmshMesh: cant save single face"); + // return {}; + // } + // context.io_system.read(faceOut, "Wavefront .obj file", {}); } std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; -- Gitee From 9ebcc4bdfa501b1620c1f915cf3dc2d1b7d6cb89 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Mon, 8 Jun 2026 15:14:10 +0800 Subject: [PATCH 07/21] =?UTF-8?q?test(gmsh):=E5=A2=9E=E5=8A=A0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/test/CMakeLists.txt | 20 ++++ plugins/algo/GmshPlugin/test/GmshDemo.cpp | 106 ++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 plugins/algo/GmshPlugin/test/GmshDemo.cpp diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt index d8f79bd..c9c027d 100644 --- a/plugins/algo/GmshPlugin/test/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -23,6 +23,26 @@ target_link_libraries(GmshVtkExample PRIVATE VTK::RenderingOpenGL2 VTK::FiltersGeometry ) + +add_executable(GmshDemo "GmshDemo.cpp") +target_link_libraries(GmshDemo PRIVATE + gmsh::shared + Data + GmshPluginlib + VTK::InteractionStyle + VTK::CommonColor + VTK::RenderingCore + VTK::RenderingUI + VTK::RenderingOpenGL2 + VTK::FiltersGeometry +) +add_custom_command(TARGET GmshDemo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMENT "Copying gmsh.dll to GmshDemo output directory" +) + vtk_module_autoinit(TARGETS GmshVtkExample MODULES VTK::InteractionStyle VTK::CommonColor diff --git a/plugins/algo/GmshPlugin/test/GmshDemo.cpp b/plugins/algo/GmshPlugin/test/GmshDemo.cpp new file mode 100644 index 0000000..39e66fe --- /dev/null +++ b/plugins/algo/GmshPlugin/test/GmshDemo.cpp @@ -0,0 +1,106 @@ +#include "gmsh.h" +#include +#include +#include +#include +#include + +int main() +{ + if (false) { + // 定义矩形的四个角点 + double lc = 0.1; // 网格特征尺寸 + int p1 = gmsh::model::occ::addPoint(0, 0, 0, lc); + int p2 = gmsh::model::occ::addPoint(1, 0, 0, lc); + int p3 = gmsh::model::occ::addPoint(1, 1, 0, lc); + int p4 = gmsh::model::occ::addPoint(0, 1, 0, lc); + + // 定义四条边 + int l1 = gmsh::model::occ::addLine(p1, p2); + int l2 = gmsh::model::occ::addLine(p2, p3); + int l3 = gmsh::model::occ::addLine(p3, p4); + int l4 = gmsh::model::occ::addLine(p4, p1); + + // 创建曲线环 + int loop = gmsh::model::occ::addCurveLoop({ l1, l2, l3, l4 }); + + // 创建平面曲面 + int surface = gmsh::model::occ::addPlaneSurface({ loop }); + + int p5 = gmsh::model::occ::addPoint(0, 0, 0, lc); + int p6 = gmsh::model::occ::addPoint(1, 0, 0, lc); + int p7 = gmsh::model::occ::addPoint(1, 1, 0, lc); + int p8 = gmsh::model::occ::addPoint(0, 1, 0, lc); + + // 定义四条边 + int l5 = gmsh::model::occ::addLine(p5, p6); + int l6 = gmsh::model::occ::addLine(p6, p7); + int l7 = gmsh::model::occ::addLine(p7, p8); + int l8 = gmsh::model::occ::addLine(p8, p5); + + gmsh::model::occ::addPoint(0, 0, 0, lc); + gmsh::model::occ::addPoint(1, 0, 0, lc); + gmsh::model::occ::addPoint(1, 1, 0, lc); + gmsh::model::occ::addPoint(0, 1, 0, lc); + + int s1 = gmsh::model::occ::addRectangle(0, 0, 0, 10, 10); + } + // 初始化 Gmsh + gmsh::initialize(); + gmsh::option::setNumber("General.Terminal", 1); + gmsh::model::add("test_rectangle"); + + std::vector> outDimTags; + std::vector>> outDimTagsMap; + gmsh::model::occ::importShapes("E:/VSProject/_models/models/step_boundary_colors.stp", outDimTags); + gmsh::model::occ::synchronize(); + + gmsh::option::setNumber("Mesh.MeshOnlyEmpty", 1); + gmsh::option::setNumber("Mesh.MeshOnlyVisible", 1); + gmsh::option::setNumber("Mesh.SaveAll", 1); + + // 禁用从顶点继承尺寸,完全由Field控制 + gmsh::option::setNumber("Mesh.MeshSizeFromPoints", 0); + gmsh::option::setNumber("Mesh.MeshSizeFromCurvature", 0); + gmsh::option::setNumber("Mesh.MeshSizeExtendFromBoundary", 0); + // 开启后:Field设置的尺寸会自动扩展到边上 + gmsh::vectorpair faces; + gmsh::model::getEntities(faces, 2); + gmsh::model::setVisibility(faces, 0,true); + + std::set meshedEdges; + std::set meshedFaces; + + for (auto& [dim, tag] : faces) { + double size = 5.0 + tag * 10.0; // 每个face不同尺寸 + + // 用 Field 控制面内部尺寸 + // MathEval常数场 + int fieldTag = gmsh::model::mesh::field::add("MathEval"); + gmsh::model::mesh::field::setString(fieldTag, "F", + std::to_string(size)); + gmsh::model::mesh::field::setAsBackgroundMesh(fieldTag); + + // 显示并网格化当前face + gmsh::model::setVisibility({ { 2, tag } }, 1,true); + gmsh::model::mesh::generate(2); + meshedFaces.insert(tag); + + // 清理Field,避免影响下一个face + gmsh::model::mesh::field::remove(fieldTag); + + // 记录已网格化的边 + gmsh::vectorpair edges; + gmsh::model::getBoundary({ { 2, tag } }, edges, false, false, false); + for (auto& [d, t] : edges) { + if (d == 1) + meshedEdges.insert(std::abs(t)); + } + + gmsh::write("final_mesh.obj"); + int i = 0; + } + + gmsh::finalize(); + return 0; +} \ No newline at end of file -- Gitee From 265609a535240ea93a06731ed4cdd9cb496d7d3e Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Fri, 19 Jun 2026 20:11:43 +0800 Subject: [PATCH 08/21] =?UTF-8?q?refactor(gmsh):=E5=88=A0=E9=99=A4EdgeGeoF?= =?UTF-8?q?eature=EF=BC=8C=E4=BD=BF=E7=94=A8gmsh=E5=86=85=E9=83=A8?= =?UTF-8?q?=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 33 +- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 332 +----------------- plugins/algo/GmshPlugin/test/CMakeLists.txt | 15 - 3 files changed, 50 insertions(+), 330 deletions(-) diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index 5bcb13e..fb696c2 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -1,5 +1,13 @@ find_package(gmsh REQUIRED) +set(GMSH_SOURCE_DIR "" CACHE PATH "Gmsh source tree for internal API") +set(GMSH_BUILD_DIR "" CACHE PATH "Gmsh build tree for generated internal headers") + +if(NOT GMSH_SOURCE_DIR OR NOT GMSH_BUILD_DIR) + message(FATAL_ERROR + "GmshPlugin requires GMSH_SOURCE_DIR and GMSH_BUILD_DIR") +endif() + precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" @@ -8,15 +16,38 @@ precess_add_algo_plugin(GmshPlugin "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" ) + +target_include_directories(GmshPluginlib PRIVATE + "${GMSH_BUILD_DIR}/src/common" + "${GMSH_SOURCE_DIR}/src/common" + "${GMSH_SOURCE_DIR}/src/geo" + "${GMSH_SOURCE_DIR}/src/mesh" + "${GMSH_SOURCE_DIR}/src/numeric" +) + precess_plugin_link_libraries(GmshPlugin - gmsh::shared + gmsh::lib TKBRep # BRep_Builder, BRepTools TKernel # Standard_* 基础类型 TKG3d # gp_* 准则基础 TKDESTEP # STEPControl_Reader TKXSBase # STEP/IGES 转换基础 TKTopAlgo # TopExp_Explorer + TKG2d + TKDEIGES + TKOffset + TKFeat + TKFillet + TKBool + TKMesh + TKHLR + TKBO + TKPrim + TKShHealing + TKGeomAlgo + TKMath ) +target_link_libraries(GmshPluginlib PUBLIC winmm wsock32 ws2_32 psapi) add_subdirectory(test) diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index dcfc8ce..a9471a8 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -7,7 +7,8 @@ #include #include -#include +#include +#include #include #include #include @@ -16,19 +17,12 @@ #include #include #include -#include #include #include -#include -#include -#include -#include -#include #include #include -#include #include #include #include @@ -136,323 +130,33 @@ TopoDS_Compound makeFaceCompound(const TopoDS_Face& face) return compound; } -// ---- 边几何特征 ---- -struct EdgeGeoFeature { - // 两端点 - double x1, y1, z1; - double x2, y2, z2; - - // 曲线中点 - double mx, my, mz; - - // 长度 - double length = 0.0; - - // 3 个采样点 - std::vector samples; // [x0,y0,z0, x1,y1,z1, x2,y2,z2] - - static double pointDist(double ax, double ay, double az, - double bx, double by, double bz) - { - double dx = ax - bx; - double dy = ay - by; - double dz = az - bz; - return std::sqrt(dx * dx + dy * dy + dz * dz); - } - - // 无向端点距离:考虑方向相反的情况 - double endpointDistanceTo(const EdgeGeoFeature& o) const - { - double dForward = pointDist(x1, y1, z1, o.x1, o.y1, o.z1) + pointDist(x2, y2, z2, o.x2, o.y2, o.z2); - - double dReverse = pointDist(x1, y1, z1, o.x2, o.y2, o.z2) + pointDist(x2, y2, z2, o.x1, o.y1, o.z1); - - return std::min(dForward, dReverse); - } - - double midpointDistanceTo(const EdgeGeoFeature& o) const - { - return pointDist(mx, my, mz, o.mx, o.my, o.mz); - } - - double lengthDistanceTo(const EdgeGeoFeature& o) const - { - return std::abs(length - o.length); - } - - // 采样点距离,同样考虑正向/反向 - double sampleDistanceTo(const EdgeGeoFeature& o) const - { - if (samples.size() != o.samples.size() || samples.empty()) - return 0.0; - - std::size_t n = samples.size() / 3; - double dForward = 0.0; - double dReverse = 0.0; - - for (std::size_t i = 0; i < n; ++i) { - std::size_t i3 = i * 3; - std::size_t r3 = (n - 1 - i) * 3; - - dForward += pointDist( - samples[i3], samples[i3 + 1], samples[i3 + 2], - o.samples[i3], o.samples[i3 + 1], o.samples[i3 + 2]); - - dReverse += pointDist( - samples[i3], samples[i3 + 1], samples[i3 + 2], - o.samples[r3], o.samples[r3 + 1], o.samples[r3 + 2]); - } - - return std::min(dForward, dReverse); - } - - // 综合评分 - double distanceTo(const EdgeGeoFeature& o, double scale) const - { - // 防止 scale 太小 - double s = std::max(scale, 1e-12); - - double de = endpointDistanceTo(o) / s; - double dl = lengthDistanceTo(o) / s; - double dm = midpointDistanceTo(o) / s; - double ds = sampleDistanceTo(o) / s; - - // 端点权重大一些 - return 5.0 * de + 2.0 * dl + 3.0 * dm + 4.0 * ds; - } -}; - -std::vector sampleOCCEdgePoints(const TopoDS_Edge& edge, int sampleCount = 3) -{ - std::vector pts; - if (sampleCount <= 0) - return pts; - - BRepAdaptor_Curve curve(edge); - double u1 = curve.FirstParameter(); - double u2 = curve.LastParameter(); - - for (int i = 1; i <= sampleCount; ++i) { - double t = double(i) / double(sampleCount + 1); - double u = (1.0 - t) * u1 + t * u2; - gp_Pnt p = curve.Value(u); - pts.push_back(p.X()); - pts.push_back(p.Y()); - pts.push_back(p.Z()); - } - return pts; -} -std::vector sampleGmshEdgePoints(int gmshTag, int sampleCount = 3) -{ - std::vector pts; - if (sampleCount <= 0) - return pts; - - std::vector minv, maxv; - gmsh::model::getParametrizationBounds(1, gmshTag, minv, maxv); - if (minv.empty() || maxv.empty()) - return pts; - - double u1 = minv[0]; - double u2 = maxv[0]; - - for (int i = 1; i <= sampleCount; ++i) { - double t = double(i) / double(sampleCount + 1); - double u = (1.0 - t) * u1 + t * u2; - - std::vector coord; - gmsh::model::getValue(1, gmshTag, { u }, coord); - if (coord.size() >= 3) { - pts.push_back(coord[0]); - pts.push_back(coord[1]); - pts.push_back(coord[2]); - } - } - return pts; -} - -// ---- OCC 边特征 ---- -EdgeGeoFeature computeOCCEdgeFeature(GeomEdgeId gid, const IncrementalMeshContext& ctx) -{ - TopoDS_Edge edge = ctx.getEdgeByGlobalId(gid); - - TopoDS_Vertex v1, v2; - TopExp::Vertices(edge, v1, v2); - - gp_Pnt p1 = BRep_Tool::Pnt(v1); - gp_Pnt p2 = BRep_Tool::Pnt(v2); - - GProp_GProps props; - BRepGProp::LinearProperties(edge, props); - - BRepAdaptor_Curve curve(edge); - double u1 = curve.FirstParameter(); - double u2 = curve.LastParameter(); - double um = 0.5 * (u1 + u2); - gp_Pnt pm = curve.Value(um); - - EdgeGeoFeature f {}; - f.x1 = p1.X(); - f.y1 = p1.Y(); - f.z1 = p1.Z(); - f.x2 = p2.X(); - f.y2 = p2.Y(); - f.z2 = p2.Z(); - - f.mx = pm.X(); - f.my = pm.Y(); - f.mz = pm.Z(); - - f.length = props.Mass(); - f.samples = sampleOCCEdgePoints(edge, 3); - return f; -} -double getShapeScale(const TopoDS_Shape& shape) -{ - Bnd_Box box; - BRepBndLib::Add(shape, box); - - double xmin, ymin, zmin, xmax, ymax, zmax; - box.Get(xmin, ymin, zmin, xmax, ymax, zmax); - - double dx = xmax - xmin; - double dy = ymax - ymin; - double dz = zmax - zmin; - double diag = std::sqrt(dx * dx + dy * dy + dz * dz); - - if (diag < 1e-12) - diag = 1.0; - return diag; -} - -// ---- GMSH 边特征 ---- -EdgeGeoFeature computeGmshEdgeFeature(int gmshTag) -{ - EdgeGeoFeature f {}; - - // 端点 - std::vector> vtxBnd; - gmsh::model::getBoundary({ { 1, gmshTag } }, vtxBnd, false, false, false); - - if (vtxBnd.size() >= 2) { - int v0 = std::abs(vtxBnd[0].second); - int v1 = std::abs(vtxBnd[1].second); - - std::vector p0, p1, param; - gmsh::model::getValue(0, v0, param, p0); - gmsh::model::getValue(0, v1, param, p1); - - if (p0.size() >= 3 && p1.size() >= 3) { - f.x1 = p0[0]; - f.y1 = p0[1]; - f.z1 = p0[2]; - - f.x2 = p1[0]; - f.y2 = p1[1]; - f.z2 = p1[2]; - } - } - - // 长度 - gmsh::model::occ::getMass(1, gmshTag, f.length); - - // 中点 - std::vector minv, maxv; - gmsh::model::getParametrizationBounds(1, gmshTag, minv, maxv); - if (!minv.empty() && !maxv.empty()) { - double um = 0.5 * (minv[0] + maxv[0]); - std::vector coord; - gmsh::model::getValue(1, gmshTag, { um }, coord); - if (coord.size() >= 3) { - f.mx = coord[0]; - f.my = coord[1]; - f.mz = coord[2]; - } - } - - f.samples = sampleGmshEdgePoints(gmshTag, 3); - return f; -} - -// ---- 匹配 ---- +// 通过 Gmsh 内部保存的 OCC Shape 映射,将当前面的 Gmsh 曲线 tag 对应回全局 CAD 边 ID。 +// 必须在 importShapesNativePointer() 和 synchronize() 之后调用。 std::map matchGmshToOCCEdges(const TopoDS_Face& face, const IncrementalMeshContext& ctx) { - // OCC 当前 face 的边 - auto edgeInfos = ctx.getFaceEdgeInfos(face); - std::vector> occFeats; - for (const auto& info : edgeInfos) { - occFeats.push_back({ info.edgeId, computeOCCEdgeFeature(info.edgeId, ctx) }); - } - - // 只取当前 Gmsh face 的边界边,而不是整个模型所有 edge - gmsh::vectorpair gmshFaceTags; - gmsh::model::getEntities(gmshFaceTags, 2); - if (gmshFaceTags.empty()) { - spdlog::warn(" No gmsh face found when matching edges"); + GModel* model = GModel::current(); + if (!model) { + spdlog::warn(" No current Gmsh model when matching OCC edges"); return {}; } - int gmshFaceTag = gmshFaceTags[0].second; - gmsh::vectorpair gmshEdgesRaw; - gmsh::model::getBoundary({ { 2, gmshFaceTag } }, gmshEdgesRaw, false, false, false); - - std::vector gmshEdges; - std::set uniqueEdges; - for (auto& [dim, tag] : gmshEdgesRaw) { - if (dim == 1 && !uniqueEdges.count(std::abs(tag))) { - uniqueEdges.insert(std::abs(tag)); - gmshEdges.push_back(std::abs(tag)); - } - } - - // 以当前 face 尺寸作为容差尺度 - double scale = getShapeScale(face); - double acceptTol = 5e-2; // 归一化后的阈值 - std::map result; - std::set matched; - - for (int gmshTag : gmshEdges) { - auto gf = computeGmshEdgeFeature(gmshTag); - - double best = 1e30; - GeomEdgeId bestId = kInvalidGeomEdgeId; - - for (auto& [id, of] : occFeats) { - if (matched.count(id)) - continue; - - double d = gf.distanceTo(of, scale); - - spdlog::debug( - " edge candidate gmsh={} occ={} " - "endpoint={:.3e} length={:.3e} midpoint={:.3e} sample={:.3e} total={:.3e}", - gmshTag, id, - gf.endpointDistanceTo(of) / scale, - gf.lengthDistanceTo(of) / scale, - gf.midpointDistanceTo(of) / scale, - gf.sampleDistanceTo(of) / scale, - d); - - if (d < best) { - best = d; - bestId = id; - } + const auto edgeInfos = ctx.getFaceEdgeInfos(face); + for (const auto& info : edgeInfos) { + TopoDS_Edge edge = ctx.getEdgeByGlobalId(info.edgeId); + GEdge* gmshEdge = model->getEdgeForOCCShape(static_cast(&edge)); + if (!gmshEdge) { + spdlog::warn(" OCC edge {} has no Gmsh curve mapping", info.edgeId); + continue; } - if (bestId != kInvalidGeomEdgeId && best < acceptTol) { - result[gmshTag] = bestId; - matched.insert(bestId); - spdlog::info(" GMSH edge {} -> OCC edge {} (score={:.3e})", - gmshTag, bestId, best); - } else { - spdlog::warn(" GMSH edge {} UNMATCHED (best OCC={}, score={:.3e})", - gmshTag, bestId, best); - } + result[gmshEdge->tag()] = info.edgeId; + spdlog::info(" GMSH edge {} -> OCC edge {}", gmshEdge->tag(), info.edgeId); } - spdlog::info(" Matched {}/{} edges", result.size(), gmshEdges.size()); + spdlog::info(" Matched {}/{} edges through Gmsh OCC mapping", + result.size(), edgeInfos.size()); return result; } diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt index c9c027d..6b3331e 100644 --- a/plugins/algo/GmshPlugin/test/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -13,7 +13,6 @@ precess_test_link_libraries(GmshPluginTest add_executable(GmshVtkExample "GmshVtkExample.cpp") target_link_libraries(GmshVtkExample PRIVATE - gmsh::shared Data GmshPluginlib VTK::InteractionStyle @@ -26,7 +25,6 @@ target_link_libraries(GmshVtkExample PRIVATE add_executable(GmshDemo "GmshDemo.cpp") target_link_libraries(GmshDemo PRIVATE - gmsh::shared Data GmshPluginlib VTK::InteractionStyle @@ -36,13 +34,6 @@ target_link_libraries(GmshDemo PRIVATE VTK::RenderingOpenGL2 VTK::FiltersGeometry ) -add_custom_command(TARGET GmshDemo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - COMMENT "Copying gmsh.dll to GmshDemo output directory" -) - vtk_module_autoinit(TARGETS GmshVtkExample MODULES VTK::InteractionStyle VTK::CommonColor @@ -51,9 +42,3 @@ vtk_module_autoinit(TARGETS GmshVtkExample MODULES VTK::RenderingOpenGL2 VTK::FiltersGeometry ) -add_custom_command(TARGET GmshVtkExample POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - COMMENT "Copying gmsh.dll to GmshVtkExample output directory" -) -- Gitee From ebc02093d8be92a92b424bb1e04fecbc89a51b43 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 21 Jun 2026 20:12:48 +0800 Subject: [PATCH 09/21] =?UTF-8?q?feat(gmsh):=E5=A2=9E=E5=8A=A0=E5=9B=9B?= =?UTF-8?q?=E8=BE=B9=E5=BD=A2=E7=BD=91=E6=A0=BC=E5=92=8C=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8C=96=E5=9B=9B=E8=BE=B9=E5=BD=A2=E7=BD=91=E6=A0=BC=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 1 + .../algo/GmshPlugin/GmshInternalMesher.cpp | 18 ++ plugins/algo/GmshPlugin/GmshInternalMesher.h | 11 + plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 14 +- plugins/algo/GmshPlugin/GmshMeshTypes.h | 8 + .../algo/GmshPlugin/IncrementalMeshTools.cpp | 194 ++++++++++++++++-- .../algo/GmshPlugin/IncrementalMeshTools.h | 6 +- 7 files changed, 228 insertions(+), 24 deletions(-) create mode 100644 plugins/algo/GmshPlugin/GmshInternalMesher.cpp create mode 100644 plugins/algo/GmshPlugin/GmshInternalMesher.h create mode 100644 plugins/algo/GmshPlugin/GmshMeshTypes.h diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index fb696c2..5b380ff 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -12,6 +12,7 @@ precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" "GmshIncrementalMeshState.cpp" + "GmshInternalMesher.cpp" "IncrementalMeshContext.cpp" "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" diff --git a/plugins/algo/GmshPlugin/GmshInternalMesher.cpp b/plugins/algo/GmshPlugin/GmshInternalMesher.cpp new file mode 100644 index 0000000..500776d --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshInternalMesher.cpp @@ -0,0 +1,18 @@ +#include "GmshInternalMesher.h" + +#include "GEdge.h" +#include "GModel.h" + +namespace GmshInternalMesher { + +int findEdgeTag(const TopoDS_Edge& edge) +{ + GModel* model = GModel::current(); + if (!model) + return 0; + + GEdge* gmshEdge = model->getEdgeForOCCShape(static_cast(&edge)); + return gmshEdge ? gmshEdge->tag() : 0; +} + +} // namespace GmshInternalMesher diff --git a/plugins/algo/GmshPlugin/GmshInternalMesher.h b/plugins/algo/GmshPlugin/GmshInternalMesher.h new file mode 100644 index 0000000..c9c446f --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshInternalMesher.h @@ -0,0 +1,11 @@ +#pragma once + +class TopoDS_Edge; + +namespace GmshInternalMesher { + +// 通过 Gmsh 内部 OCC Shape 映射返回曲线 tag;未找到时返回 0。 +// 必须在 importShapesNativePointer() 和 synchronize() 之后调用。 +int findEdgeTag(const TopoDS_Edge& edge); + +} // namespace GmshInternalMesher diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 4d9063f..27151f4 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -25,6 +25,7 @@ std::any systems::algo::GmshMeshHandler::execute( int faceIndex = -1; double meshSize = 0.0; int operationMode = 1; + int meshTypeIndex = 0; if (args.size() >= 1) { const std::string* idxStr = args[0].get(); @@ -59,6 +60,12 @@ std::any systems::algo::GmshMeshHandler::execute( } } + if (args.size() >= 4) { + const int* value = args[3].get(); + if (value) + meshTypeIndex = *value; + } + if (faceIndex < 0) { spdlog::error("GmshMesh: need face id"); return {}; @@ -115,7 +122,7 @@ std::any systems::algo::GmshMeshHandler::execute( } spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, meshSize); + *meshData, *geometry, state, modelLayer, faceKey, meshSize, meshTypeIndex); } else if (operationMode == 2) { spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); if (IncrementalMeshTools::deleteFaceMesh(*meshData, state, modelLayer, faceKey)) @@ -123,7 +130,7 @@ std::any systems::algo::GmshMeshHandler::execute( } else if (operationMode == 3) { spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, meshSize); + *meshData, *geometry, state, modelLayer, faceKey, meshSize, meshTypeIndex); } else { spdlog::warn("GmshMesh: unknown operation mode {}, skip", operationMode); return {}; @@ -176,6 +183,7 @@ std::vector systems::algo::GmshMeshHandler::args_type() const return { ArgType { ArgTypeEnum::Text, "面索引(0开始)", "" }, ArgType { ArgTypeEnum::Text, "网格尺寸(留空自动)", "" }, - ArgType { ArgTypeEnum::Text, "1: mesh 2: delete 3: remesh", "" } + ArgType { ArgTypeEnum::Text, "1: mesh 2: delete 3: remesh", "" }, + ArgType { ArgTypeEnum::Combo, "网格类型", "三角形,四边形主导,结构化四边形" } }; } diff --git a/plugins/algo/GmshPlugin/GmshMeshTypes.h b/plugins/algo/GmshPlugin/GmshMeshTypes.h new file mode 100644 index 0000000..af0ca8e --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshMeshTypes.h @@ -0,0 +1,8 @@ +#pragma once + +// Gmsh 曲面网格类型。数值需要和前端 Combo 的选项顺序保持一致。 +enum class GmshSurfaceMeshType { + Triangle = 0, + QuadDominant = 1, + StructuredQuadrilateral = 2 +}; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index a9471a8..8573f1e 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -2,13 +2,13 @@ #include "IncrementalMeshContext.h" #include "GeometryRegistry.h" +#include "GmshInternalMesher.h" +#include "GmshMeshTypes.h" #include "MeshData.h" #include "ModelLayer.h" #include #include -#include -#include #include #include #include @@ -130,29 +130,44 @@ TopoDS_Compound makeFaceCompound(const TopoDS_Face& face) return compound; } -// 通过 Gmsh 内部保存的 OCC Shape 映射,将当前面的 Gmsh 曲线 tag 对应回全局 CAD 边 ID。 -// 必须在 importShapesNativePointer() 和 synchronize() 之后调用。 +// 将前端 Combo 索引转换为内部曲面网格类型。 +GmshSurfaceMeshType parseSurfaceMeshType(int meshTypeIndex) +{ + if (meshTypeIndex == static_cast(GmshSurfaceMeshType::QuadDominant)) + return GmshSurfaceMeshType::QuadDominant; + if (meshTypeIndex == static_cast(GmshSurfaceMeshType::StructuredQuadrilateral)) + return GmshSurfaceMeshType::StructuredQuadrilateral; + if (meshTypeIndex != static_cast(GmshSurfaceMeshType::Triangle)) + spdlog::warn("GmshMesh: invalid mesh type {}, using triangle", meshTypeIndex); + return GmshSurfaceMeshType::Triangle; +} + +// 返回日志中使用的曲面网格类型名称。 +const char* surfaceMeshTypeName(GmshSurfaceMeshType meshType) +{ + if (meshType == GmshSurfaceMeshType::QuadDominant) + return "quad-dominant"; + if (meshType == GmshSurfaceMeshType::StructuredQuadrilateral) + return "structured-quad"; + return "triangle"; +} + +// 将当前面的 Gmsh 曲线 tag 对应回全局 CAD 边 ID。 std::map matchGmshToOCCEdges(const TopoDS_Face& face, const IncrementalMeshContext& ctx) { - GModel* model = GModel::current(); - if (!model) { - spdlog::warn(" No current Gmsh model when matching OCC edges"); - return {}; - } - std::map result; const auto edgeInfos = ctx.getFaceEdgeInfos(face); for (const auto& info : edgeInfos) { TopoDS_Edge edge = ctx.getEdgeByGlobalId(info.edgeId); - GEdge* gmshEdge = model->getEdgeForOCCShape(static_cast(&edge)); - if (!gmshEdge) { + int gmshTag = GmshInternalMesher::findEdgeTag(edge); + if (gmshTag <= 0) { spdlog::warn(" OCC edge {} has no Gmsh curve mapping", info.edgeId); continue; } - result[gmshEdge->tag()] = info.edgeId; - spdlog::info(" GMSH edge {} -> OCC edge {}", gmshEdge->tag(), info.edgeId); + result[gmshTag] = info.edgeId; + spdlog::info(" GMSH edge {} -> OCC edge {}", gmshTag, info.edgeId); } spdlog::info(" Matched {}/{} edges through Gmsh OCC mapping", @@ -160,6 +175,135 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, return result; } +// 保存结构化划分中一条边的节点数,以及该数量是否由共享边缓存固定。 +struct EdgeTransfiniteInfo { + int gmshTag {}; + int pointCount {}; + bool fixedByExistingMesh {}; +}; + +// 根据曲线长度和目标网格尺寸估算节点数,并延续原实现的偶数节点约束。 +int estimateEdgePointCount(int gmshTag, double meshSize) +{ + double length = 0.0; + gmsh::model::occ::getMass(1, gmshTag, length); + + int pointCount = static_cast(std::ceil(length / meshSize)) + 2; + if (pointCount < 2) + pointCount = 2; + if (pointCount % 2 == 1) + ++pointCount; + return pointCount; +} + +// 优先采用已有共享边节点数,否则根据曲线长度和网格尺寸估算。 +EdgeTransfiniteInfo makeEdgeTransfiniteInfo( + int gmshTag, + const std::map& gmshToOcc, + const GmshIncrementalMeshState& state, + double meshSize) +{ + EdgeTransfiniteInfo info; + info.gmshTag = gmshTag; + + auto occIt = gmshToOcc.find(gmshTag); + if (occIt != gmshToOcc.end()) { + auto cacheIt = state.meshedEdgesCache.find(occIt->second); + if (cacheIt != state.meshedEdgesCache.end() && !cacheIt->second.empty()) { + info.pointCount = static_cast(cacheIt->second.nodeCount()); + info.fixedByExistingMesh = true; + return info; + } + } + + info.pointCount = estimateEdgePointCount(gmshTag, meshSize); + return info; +} + +// 协调一对相对边的节点数;两条共享边节点数不一致时拒绝结构化划分。 +bool resolveOppositeEdgePointCount( + const EdgeTransfiniteInfo& first, + const EdgeTransfiniteInfo& opposite, + int& pointCount) +{ + if (first.fixedByExistingMesh && opposite.fixedByExistingMesh) { + if (first.pointCount != opposite.pointCount) { + spdlog::warn("GmshMesh: structured quad rejected, opposite edges {} and {} have {} / {} points", + first.gmshTag, opposite.gmshTag, + first.pointCount, opposite.pointCount); + return false; + } + pointCount = first.pointCount; + return true; + } + + if (first.fixedByExistingMesh) { + pointCount = first.pointCount; + return true; + } + if (opposite.fixedByExistingMesh) { + pointCount = opposite.pointCount; + return true; + } + + pointCount = static_cast((first.pointCount + opposite.pointCount) * 0.5 + 0.5); + if (pointCount < 2) + pointCount = 2; + if (pointCount % 2 == 1) + ++pointCount; + return true; +} + +// 使用 Gmsh 公开 API 配置四边形主导或结构化四边形约束。 +bool configureSurfaceMeshType( + int faceTag, + GmshSurfaceMeshType meshType, + const std::map& gmshToOcc, + const GmshIncrementalMeshState& state, + double meshSize) +{ + if (meshType == GmshSurfaceMeshType::Triangle) + return true; + + if (meshType == GmshSurfaceMeshType::QuadDominant) { + gmsh::model::mesh::setRecombine(2, faceTag, 45.0); + return true; + } + + gmsh::vectorpair boundary; + gmsh::model::getBoundary({ { 2, faceTag } }, boundary, false, false, false); + + std::vector edgeTags; + for (const auto& [dim, tag] : boundary) { + if (dim == 1) + edgeTags.push_back(std::abs(tag)); + } + if (edgeTags.size() != 4) { + spdlog::warn("GmshMesh: structured quad requires 4 boundary edges, surface {} has {}", + faceTag, edgeTags.size()); + return false; + } + + std::array edges {}; + for (std::size_t i = 0; i < edges.size(); ++i) + edges[i] = makeEdgeTransfiniteInfo(edgeTags[i], gmshToOcc, state, meshSize); + + int firstPairPointCount = 0; + int secondPairPointCount = 0; + if (!resolveOppositeEdgePointCount(edges[0], edges[2], firstPairPointCount)) + return false; + if (!resolveOppositeEdgePointCount(edges[1], edges[3], secondPairPointCount)) + return false; + + gmsh::model::mesh::setTransfiniteCurve(edges[0].gmshTag, firstPairPointCount); + gmsh::model::mesh::setTransfiniteCurve(edges[2].gmshTag, firstPairPointCount); + gmsh::model::mesh::setTransfiniteCurve(edges[1].gmshTag, secondPairPointCount); + gmsh::model::mesh::setTransfiniteCurve(edges[3].gmshTag, secondPairPointCount); + gmsh::model::mesh::setTransfiniteSurface(faceTag); + gmsh::model::mesh::setRecombine(2, faceTag, 45.0); + return true; +} + // ---- 注入约束边 ---- bool injectConstrainedEdge(int gmshTag, const MeshedEdgeData& nodes, @@ -456,9 +600,11 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, - double meshSize) + double meshSize, + int meshTypeIndex) { SingleFaceMeshResult result; + GmshSurfaceMeshType meshType = parseSurfaceMeshType(meshTypeIndex); if (!state.meshContext) { spdlog::error("meshContext null, call initMeshing first"); @@ -469,8 +615,9 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( return result; } - spdlog::info("=== Meshing face {}/{} (size={:.6f}) ===", - faceIndex + 1, state.meshContext->faceCount(), meshSize); + spdlog::info("=== Meshing face {}/{} (size={:.6f}, type={}) ===", + faceIndex + 1, state.meshContext->faceCount(), meshSize, + surfaceMeshTypeName(meshType)); TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); if (face.IsNull()) { @@ -519,8 +666,15 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::option::setNumber("Mesh.MeshOnlyEmpty", 1); gmsh::option::setNumber("Mesh.SaveAll", 1); gmsh::option::setNumber("Mesh.Algorithm", 6); + gmsh::option::setNumber("Mesh.RecombineAll", 0); try { + if (!configureSurfaceMeshType(faceTag, meshType, gmshToOcc, state, meshSize)) { + spdlog::warn(" Cannot configure {} mesh", surfaceMeshTypeName(meshType)); + storeNewEdges(state, gmshToOcc); + gmsh::finalize(); + return result; + } gmsh::model::mesh::generate(2); } catch (const std::exception& e) { spdlog::error(" Mesh failed: {}", e.what()); @@ -723,11 +877,13 @@ SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, - double meshSize) + double meshSize, + int meshTypeIndex) { if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) { spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); deleteFaceMesh(mesh_data, state, model_layer, faceIndex); } - return meshSingleFace(mesh_data, geometry, state, model_layer, faceIndex, meshSize); + return meshSingleFace( + mesh_data, geometry, state, model_layer, faceIndex, meshSize, meshTypeIndex); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index ce2aff1..5041490 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -25,7 +25,8 @@ SingleFaceMeshResult meshSingleFace( GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, - double meshSize); + double meshSize, + int meshTypeIndex = 0); SingleFaceMeshResult remeshSingleFace( MeshData& mesh_data, @@ -33,7 +34,8 @@ SingleFaceMeshResult remeshSingleFace( GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, - double meshSize); + double meshSize, + int meshTypeIndex = 0); bool deleteFaceMesh( MeshData& mesh_data, -- Gitee From ccc300211d050858b4d98c2de97bbcd3677af0ed Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 21 Jun 2026 21:24:22 +0800 Subject: [PATCH 10/21] =?UTF-8?q?refeactor(gmsh):=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E9=80=90=E6=9D=A1=E5=AF=BC=E5=85=A5=E5=BD=93=E5=89=8D=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=20OCC=20=E8=BE=B9=EF=BC=8C=E5=B9=B6=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=85=AC=E5=BC=80=20API=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=9A=84=20tag=20=E5=BB=BA=E7=AB=8B=20CAD=20=E8=BE=B9=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 34 +------------------ .../algo/GmshPlugin/GmshInternalMesher.cpp | 18 ---------- plugins/algo/GmshPlugin/GmshInternalMesher.h | 11 ------ .../algo/GmshPlugin/IncrementalMeshTools.cpp | 29 +++++++++++----- plugins/algo/GmshPlugin/test/CMakeLists.txt | 15 ++++++++ 5 files changed, 37 insertions(+), 70 deletions(-) delete mode 100644 plugins/algo/GmshPlugin/GmshInternalMesher.cpp delete mode 100644 plugins/algo/GmshPlugin/GmshInternalMesher.h diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index 5b380ff..c000a0d 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -1,54 +1,22 @@ find_package(gmsh REQUIRED) -set(GMSH_SOURCE_DIR "" CACHE PATH "Gmsh source tree for internal API") -set(GMSH_BUILD_DIR "" CACHE PATH "Gmsh build tree for generated internal headers") - -if(NOT GMSH_SOURCE_DIR OR NOT GMSH_BUILD_DIR) - message(FATAL_ERROR - "GmshPlugin requires GMSH_SOURCE_DIR and GMSH_BUILD_DIR") -endif() - precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" "GmshIncrementalMeshState.cpp" - "GmshInternalMesher.cpp" "IncrementalMeshContext.cpp" "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" ) -target_include_directories(GmshPluginlib PRIVATE - "${GMSH_BUILD_DIR}/src/common" - "${GMSH_SOURCE_DIR}/src/common" - "${GMSH_SOURCE_DIR}/src/geo" - "${GMSH_SOURCE_DIR}/src/mesh" - "${GMSH_SOURCE_DIR}/src/numeric" -) - precess_plugin_link_libraries(GmshPlugin - gmsh::lib + gmsh::shared TKBRep # BRep_Builder, BRepTools TKernel # Standard_* 基础类型 TKG3d # gp_* 准则基础 TKDESTEP # STEPControl_Reader TKXSBase # STEP/IGES 转换基础 TKTopAlgo # TopExp_Explorer - TKG2d - TKDEIGES - TKOffset - TKFeat - TKFillet - TKBool - TKMesh - TKHLR - TKBO - TKPrim - TKShHealing - TKGeomAlgo - TKMath ) -target_link_libraries(GmshPluginlib PUBLIC winmm wsock32 ws2_32 psapi) - add_subdirectory(test) diff --git a/plugins/algo/GmshPlugin/GmshInternalMesher.cpp b/plugins/algo/GmshPlugin/GmshInternalMesher.cpp deleted file mode 100644 index 500776d..0000000 --- a/plugins/algo/GmshPlugin/GmshInternalMesher.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "GmshInternalMesher.h" - -#include "GEdge.h" -#include "GModel.h" - -namespace GmshInternalMesher { - -int findEdgeTag(const TopoDS_Edge& edge) -{ - GModel* model = GModel::current(); - if (!model) - return 0; - - GEdge* gmshEdge = model->getEdgeForOCCShape(static_cast(&edge)); - return gmshEdge ? gmshEdge->tag() : 0; -} - -} // namespace GmshInternalMesher diff --git a/plugins/algo/GmshPlugin/GmshInternalMesher.h b/plugins/algo/GmshPlugin/GmshInternalMesher.h deleted file mode 100644 index c9c446f..0000000 --- a/plugins/algo/GmshPlugin/GmshInternalMesher.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -class TopoDS_Edge; - -namespace GmshInternalMesher { - -// 通过 Gmsh 内部 OCC Shape 映射返回曲线 tag;未找到时返回 0。 -// 必须在 importShapesNativePointer() 和 synchronize() 之后调用。 -int findEdgeTag(const TopoDS_Edge& edge); - -} // namespace GmshInternalMesher diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 8573f1e..2eb0952 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -2,7 +2,6 @@ #include "IncrementalMeshContext.h" #include "GeometryRegistry.h" -#include "GmshInternalMesher.h" #include "GmshMeshTypes.h" #include "MeshData.h" #include "ModelLayer.h" @@ -152,17 +151,30 @@ const char* surfaceMeshTypeName(GmshSurfaceMeshType meshType) return "triangle"; } -// 将当前面的 Gmsh 曲线 tag 对应回全局 CAD 边 ID。 -std::map matchGmshToOCCEdges(const TopoDS_Face& face, +// 逐条导入当前面的 OCC 边,并直接使用公开 API 返回的 tag 建立 CAD 边映射。 +// 后续导入整个面时,Gmsh 会通过内部 Shape 映射复用这些已经绑定的曲线 tag。 +std::map importGmshEdges(const TopoDS_Face& face, const IncrementalMeshContext& ctx) { std::map result; const auto edgeInfos = ctx.getFaceEdgeInfos(face); for (const auto& info : edgeInfos) { TopoDS_Edge edge = ctx.getEdgeByGlobalId(info.edgeId); - int gmshTag = GmshInternalMesher::findEdgeTag(edge); + + gmsh::vectorpair edgeDimTags; + gmsh::model::occ::importShapesNativePointer( + static_cast(&edge), edgeDimTags); + + int gmshTag = 0; + for (const auto& [dim, tag] : edgeDimTags) { + if (dim == 1) { + gmshTag = tag; + break; + } + } + if (gmshTag <= 0) { - spdlog::warn(" OCC edge {} has no Gmsh curve mapping", info.edgeId); + spdlog::warn(" Cannot import OCC edge {}", info.edgeId); continue; } @@ -170,7 +182,7 @@ std::map matchGmshToOCCEdges(const TopoDS_Face& face, spdlog::info(" GMSH edge {} -> OCC edge {}", gmshTag, info.edgeId); } - spdlog::info(" Matched {}/{} edges through Gmsh OCC mapping", + spdlog::info(" Imported {}/{} OCC edges", result.size(), edgeInfos.size()); return result; } @@ -630,6 +642,9 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::option::setNumber("General.Terminal", 1); gmsh::model::add("face_model"); + // 先逐边导入并记录返回 tag,再导入整个面;面导入会复用相同 OCC 边的 tag。 + auto gmshToOcc = importGmshEdges(face, *state.meshContext); + gmsh::vectorpair outDimTags; gmsh::model::occ::importShapesNativePointer( static_cast(&compound), outDimTags); @@ -644,8 +659,6 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } int faceTag = faceDimTags[0].second; - auto gmshToOcc = matchGmshToOCCEdges(face, *state.meshContext); - std::size_t nodeCounter = 1, elemCounter = 1; int shared = 0, free = 0; std::map vtxNodeMap; diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt index 6b3331e..c9c027d 100644 --- a/plugins/algo/GmshPlugin/test/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -13,6 +13,7 @@ precess_test_link_libraries(GmshPluginTest add_executable(GmshVtkExample "GmshVtkExample.cpp") target_link_libraries(GmshVtkExample PRIVATE + gmsh::shared Data GmshPluginlib VTK::InteractionStyle @@ -25,6 +26,7 @@ target_link_libraries(GmshVtkExample PRIVATE add_executable(GmshDemo "GmshDemo.cpp") target_link_libraries(GmshDemo PRIVATE + gmsh::shared Data GmshPluginlib VTK::InteractionStyle @@ -34,6 +36,13 @@ target_link_libraries(GmshDemo PRIVATE VTK::RenderingOpenGL2 VTK::FiltersGeometry ) +add_custom_command(TARGET GmshDemo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMENT "Copying gmsh.dll to GmshDemo output directory" +) + vtk_module_autoinit(TARGETS GmshVtkExample MODULES VTK::InteractionStyle VTK::CommonColor @@ -42,3 +51,9 @@ vtk_module_autoinit(TARGETS GmshVtkExample MODULES VTK::RenderingOpenGL2 VTK::FiltersGeometry ) +add_custom_command(TARGET GmshVtkExample POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMENT "Copying gmsh.dll to GmshVtkExample output directory" +) -- Gitee From 30f2b96aac1ad2bea6c58190ce8644aaa3b78e0f Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 21 Jun 2026 21:26:32 +0800 Subject: [PATCH 11/21] =?UTF-8?q?refactor(gmsh):=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=8C=E9=80=9A?= =?UTF-8?q?=E8=BF=87GeometryRegistry=E8=8E=B7=E5=8F=96TopoDS=5FShape?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 1 - .../GmshPlugin/GmshIncrementalMeshState.cpp | 3 - .../GmshPlugin/GmshIncrementalMeshState.h | 6 +- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 16 +-- .../GmshPlugin/IncrementalMeshContext.cpp | 99 -------------- .../algo/GmshPlugin/IncrementalMeshContext.h | 46 ------- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 122 ++++++++++++------ .../algo/GmshPlugin/IncrementalMeshTools.h | 3 +- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 12 +- 9 files changed, 98 insertions(+), 210 deletions(-) delete mode 100644 plugins/algo/GmshPlugin/IncrementalMeshContext.cpp delete mode 100644 plugins/algo/GmshPlugin/IncrementalMeshContext.h diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index c000a0d..9867564 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -4,7 +4,6 @@ precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" "GmshIncrementalMeshState.cpp" - "IncrementalMeshContext.cpp" "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" ) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp index d9ac095..c5b2721 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp @@ -1,13 +1,10 @@ #include "GmshIncrementalMeshState.h" -#include "IncrementalMeshContext.h" - GmshIncrementalMeshState::GmshIncrementalMeshState() = default; GmshIncrementalMeshState::~GmshIncrementalMeshState() = default; void GmshIncrementalMeshState::clear() { - meshContext.reset(); meshedEdgesCache.clear(); meshedFacesCache.clear(); meshedEdgeRefCounts.clear(); diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h index 70eae99..0673e46 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -4,11 +4,8 @@ #include #include -#include #include -class IncrementalMeshContext; - // 保存一条已生成网格的 CAD 边,供相邻面复用边界节点。 struct MeshedEdgeData { std::vector coords; @@ -31,8 +28,7 @@ struct GmshIncrementalMeshState { GmshIncrementalMeshState(); ~GmshIncrementalMeshState(); - std::unique_ptr meshContext; - // 已划分 CAD 边的节点缓存,key 使用 pr-38 的全局 CAD 边 ID。 + // 已划分 CAD 边的节点缓存,key 使用 geometry 的全局 CAD 边 ID。 std::map meshedEdgesCache; std::map meshedFacesCache; // 已划分 CAD 边被多少个面复用,用于删除面网格时判断是否清理边缓存。 diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 27151f4..82e5117 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -4,7 +4,6 @@ #include "ComponentData.h" #include "ComponentOperator.h" #include "GeometryData.h" -#include "IncrementalMeshContext.h" #include "IncrementalMeshTools.h" #include "MeshData.h" #include "ModelIOSystemBase.h" @@ -93,16 +92,8 @@ std::any systems::algo::GmshMeshHandler::execute( GmshIncrementalMeshState& state = component_states_[context.cur_component.componentId()]; - // 首次执行时建立 OCC 面/边索引,后续单面划分复用该上下文。 - if (!state.meshContext) { - spdlog::info("GmshMesh: init occId..."); - state.meshContext = std::make_unique(*geometry, modelLayer.geomRegistry()); - spdlog::info("GmshMesh: {} face, {} global edge", - state.meshContext->faceCount(), - state.meshContext->globalEdgeCount()); - } - - std::size_t totalFaces = state.meshContext->faceCount(); + geometry->ensureCadIndexBuilt(modelLayer.geomRegistry()); + std::size_t totalFaces = IncrementalMeshTools::faceCount(*geometry); if (static_cast(faceIndex) >= totalFaces) { spdlog::error("GmshMesh: face id {} out of range (total {} face)", faceIndex, totalFaces); @@ -125,7 +116,8 @@ std::any systems::algo::GmshMeshHandler::execute( *meshData, *geometry, state, modelLayer, faceKey, meshSize, meshTypeIndex); } else if (operationMode == 2) { spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); - if (IncrementalMeshTools::deleteFaceMesh(*meshData, state, modelLayer, faceKey)) + if (IncrementalMeshTools::deleteFaceMesh( + *meshData, *geometry, state, modelLayer, faceKey)) spdlog::info("GmshMesh: delete face {} success", faceIndex); } else if (operationMode == 3) { spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp b/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp deleted file mode 100644 index dfec9b5..0000000 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "IncrementalMeshContext.h" - -#include "GeometryData.h" -#include "GeometryRegistry.h" -#include "GeometrySubshapeIndex.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace { - -int edgeTypeIndex() -{ - return GeometrySubshapeIndex::typeIndex(TopAbs_EDGE); -} - -int faceTypeIndex() -{ - return GeometrySubshapeIndex::typeIndex(TopAbs_FACE); -} - -} // namespace - -IncrementalMeshContext::IncrementalMeshContext(GeometryData& geometry, GeometryRegistry& registry) - : cad_index_(&geometry.cad_index) - , registry_(®istry) -{ - geometry.ensureCadIndexBuilt(registry); - - // 计算每个 CAD face 的边集合,后续 Gmsh 单面匹配只查这个拓扑缓存。 - const auto& faceMap = cad_index_->type_maps[faceTypeIndex()]; - const auto& edgeMap = cad_index_->type_maps[edgeTypeIndex()]; - face_edge_infos_.resize(static_cast(faceMap.Extent())); - - for (int faceIdx = 1; faceIdx <= faceMap.Extent(); ++faceIdx) { - auto face = TopoDS::Face(faceMap(faceIdx)); - std::set seen; - - for (TopExp_Explorer ex(face, TopAbs_EDGE); ex.More(); ex.Next()) { - int localEdgeId = edgeMap.FindIndex(ex.Current()); - GeomEdgeId gid = cad_index_->edgeGlobalId(localEdgeId); - if (gid != kInvalidGeomEdgeId && seen.insert(gid).second) - face_edge_infos_[static_cast(faceIdx - 1)].push_back({ gid, localEdgeId }); - } - } -} - -IncrementalMeshContext::~IncrementalMeshContext() = default; - -int IncrementalMeshContext::globalEdgeCount() const -{ - return cad_index_->type_maps[edgeTypeIndex()].Extent(); -} - -std::vector IncrementalMeshContext::getFaceEdgeInfos(const TopoDS_Face& face) const -{ - int localFaceId = cad_index_->type_maps[faceTypeIndex()].FindIndex(face); - if (localFaceId < 1 || static_cast(localFaceId) > face_edge_infos_.size()) - return {}; - return face_edge_infos_[static_cast(localFaceId - 1)]; -} - -std::vector IncrementalMeshContext::getFaceEdgeIds(const TopoDS_Face& face) const -{ - std::vector ids; - for (const auto& info : getFaceEdgeInfos(face)) - ids.push_back(info.edgeId); - return ids; -} - -TopoDS_Edge IncrementalMeshContext::getEdgeByGlobalId(GeomEdgeId globalId) const -{ - const TopoDS_Shape* shape = registry_->getEdge(globalId); - if (!shape) - throw std::out_of_range("invalid CAD edge ID " + std::to_string(globalId)); - return TopoDS::Edge(*shape); -} - -std::size_t IncrementalMeshContext::faceCount() const -{ - return static_cast(cad_index_->type_maps[faceTypeIndex()].Extent()); -} - -TopoDS_Face IncrementalMeshContext::getFaceByIndex(std::size_t index) const -{ - int occIndex = static_cast(index) + 1; - const auto& faceMap = cad_index_->type_maps[faceTypeIndex()]; - if (occIndex < 1 || occIndex > faceMap.Extent()) - throw std::out_of_range("face index " + std::to_string(index) + " out of range"); - return TopoDS::Face(faceMap(occIndex)); -} diff --git a/plugins/algo/GmshPlugin/IncrementalMeshContext.h b/plugins/algo/GmshPlugin/IncrementalMeshContext.h deleted file mode 100644 index ace2abc..0000000 --- a/plugins/algo/GmshPlugin/IncrementalMeshContext.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "Core.h" - -#include - -class TopoDS_Face; -class TopoDS_Edge; -struct GeometryData; -struct GeometrySubshapeIndex; -class GeometryRegistry; - -// 记录一个 CAD 面上的边拓扑关系,供 Gmsh 单面网格匹配时限定候选边集合。 -struct FaceEdgeInfo { - GeomEdgeId edgeId { kInvalidGeomEdgeId }; - int localEdgeId { 0 }; -}; - -// 将 GeometryRegistry 的 CAD 子形状索引适配给 Gmsh 增量网格流程使用。 -class IncrementalMeshContext { -public: - IncrementalMeshContext(GeometryData& geometry, GeometryRegistry& registry); - ~IncrementalMeshContext(); - - IncrementalMeshContext(const IncrementalMeshContext&) = delete; - IncrementalMeshContext& operator=(const IncrementalMeshContext&) = delete; - - // 返回当前 CAD component 中注册到 GeometryRegistry 的边数量。 - int globalEdgeCount() const; - // 返回指定面包含的边拓扑信息,候选范围只来自 CAD face 本身。 - std::vector getFaceEdgeInfos(const TopoDS_Face& face) const; - // 返回指定面包含的全局 CAD 边 ID,供 Gmsh 边界复用逻辑使用。 - std::vector getFaceEdgeIds(const TopoDS_Face& face) const; - // 根据全局 CAD 边 ID 取回原始 TopoDS_Edge。 - TopoDS_Edge getEdgeByGlobalId(GeomEdgeId globalId) const; - - // 返回当前 Shape 中可独立划分的面数量。 - std::size_t faceCount() const; - // 根据 0 起始面索引取回 TopoDS_Face。 - TopoDS_Face getFaceByIndex(std::size_t index) const; - -private: - GeometrySubshapeIndex* cad_index_ {}; - GeometryRegistry* registry_ {}; - std::vector> face_edge_infos_; -}; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 2eb0952..fb97093 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -1,5 +1,4 @@ #include "IncrementalMeshTools.h" -#include "IncrementalMeshContext.h" #include "GeometryRegistry.h" #include "GmshMeshTypes.h" @@ -16,10 +15,12 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -129,6 +130,43 @@ TopoDS_Compound makeFaceCompound(const TopoDS_Face& face) return compound; } +// 返回 GeometryData 中指定索引的 CAD 面,实际 Shape 统一从 GeometryRegistry 取得。 +TopoDS_Face getFaceByIndex( + const GeometryData& geometry, + const GeometryRegistry& registry, + std::size_t faceIndex) +{ + const auto& faceMap = + geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)]; + int localFaceId = static_cast(faceIndex) + 1; + if (localFaceId < 1 || localFaceId > faceMap.Extent()) + return {}; + + GeomFaceId globalFaceId = geometry.cad_index.faceGlobalId(localFaceId); + const TopoDS_Shape* shape = registry.getFace(globalFaceId); + return shape ? TopoDS::Face(*shape) : TopoDS_Face {}; +} + +// 遍历一个 CAD 面,返回其去重后的全局边 ID。 +std::vector getFaceEdgeIds( + const TopoDS_Face& face, + const GeometryData& geometry) +{ + const auto& edgeMap = + geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; + + std::vector edgeIds; + for (TopExp_Explorer explorer(face, TopAbs_EDGE); explorer.More(); explorer.Next()) { + int localEdgeId = edgeMap.FindIndex(explorer.Current()); + GeomEdgeId globalEdgeId = geometry.cad_index.edgeGlobalId(localEdgeId); + if (globalEdgeId != kInvalidGeomEdgeId + && std::find(edgeIds.begin(), edgeIds.end(), globalEdgeId) == edgeIds.end()) { + edgeIds.push_back(globalEdgeId); + } + } + return edgeIds; +} + // 将前端 Combo 索引转换为内部曲面网格类型。 GmshSurfaceMeshType parseSurfaceMeshType(int meshTypeIndex) { @@ -154,12 +192,22 @@ const char* surfaceMeshTypeName(GmshSurfaceMeshType meshType) // 逐条导入当前面的 OCC 边,并直接使用公开 API 返回的 tag 建立 CAD 边映射。 // 后续导入整个面时,Gmsh 会通过内部 Shape 映射复用这些已经绑定的曲线 tag。 std::map importGmshEdges(const TopoDS_Face& face, - const IncrementalMeshContext& ctx) + const GeometryData& geometry) { std::map result; - const auto edgeInfos = ctx.getFaceEdgeInfos(face); - for (const auto& info : edgeInfos) { - TopoDS_Edge edge = ctx.getEdgeByGlobalId(info.edgeId); + const auto& edgeMap = + geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; + std::vector importedEdgeIds; + + for (TopExp_Explorer explorer(face, TopAbs_EDGE); explorer.More(); explorer.Next()) { + TopoDS_Edge edge = TopoDS::Edge(explorer.Current()); + int localEdgeId = edgeMap.FindIndex(edge); + GeomEdgeId globalEdgeId = geometry.cad_index.edgeGlobalId(localEdgeId); + if (globalEdgeId == kInvalidGeomEdgeId + || std::find(importedEdgeIds.begin(), importedEdgeIds.end(), globalEdgeId) != importedEdgeIds.end()) { + continue; + } + importedEdgeIds.push_back(globalEdgeId); gmsh::vectorpair edgeDimTags; gmsh::model::occ::importShapesNativePointer( @@ -174,16 +222,16 @@ std::map importGmshEdges(const TopoDS_Face& face, } if (gmshTag <= 0) { - spdlog::warn(" Cannot import OCC edge {}", info.edgeId); + spdlog::warn(" Cannot import OCC edge {}", globalEdgeId); continue; } - result[gmshTag] = info.edgeId; - spdlog::info(" GMSH edge {} -> OCC edge {}", gmshTag, info.edgeId); + result[gmshTag] = globalEdgeId; + spdlog::info(" GMSH edge {} -> OCC edge {}", gmshTag, globalEdgeId); } spdlog::info(" Imported {}/{} OCC edges", - result.size(), edgeInfos.size()); + result.size(), importedEdgeIds.size()); return result; } @@ -599,11 +647,13 @@ bool IncrementalMeshTools::initMeshing(const std::string& stepFile, spdlog::error("Failed to load: {}", stepFile); return false; } - state.meshContext = std::make_unique(geometry, registry); + geometry.ensureCadIndexBuilt(registry); + std::size_t faceCount = IncrementalMeshTools::faceCount(geometry); + std::size_t edgeCount = static_cast( + geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)].Extent()); spdlog::info("Loaded: {} faces, {} global edges", - state.meshContext->faceCount(), - state.meshContext->globalEdgeCount()); - return state.meshContext->faceCount() > 0; + faceCount, edgeCount); + return faceCount > 0; } SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( @@ -618,20 +668,18 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( SingleFaceMeshResult result; GmshSurfaceMeshType meshType = parseSurfaceMeshType(meshTypeIndex); - if (!state.meshContext) { - spdlog::error("meshContext null, call initMeshing first"); - return result; - } - if (faceIndex >= state.meshContext->faceCount()) { + std::size_t totalFaces = IncrementalMeshTools::faceCount(geometry); + if (faceIndex >= totalFaces) { spdlog::error("faceIndex {} out of range", faceIndex); return result; } spdlog::info("=== Meshing face {}/{} (size={:.6f}, type={}) ===", - faceIndex + 1, state.meshContext->faceCount(), meshSize, + faceIndex + 1, totalFaces, meshSize, surfaceMeshTypeName(meshType)); - TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); + TopoDS_Face face = getFaceByIndex( + geometry, model_layer.geomRegistry(), faceIndex); if (face.IsNull()) { spdlog::error("Face {} is null or invalid", faceIndex); return result; @@ -643,7 +691,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::model::add("face_model"); // 先逐边导入并记录返回 tag,再导入整个面;面导入会复用相同 OCC 边的 tag。 - auto gmshToOcc = importGmshEdges(face, *state.meshContext); + auto gmshToOcc = importGmshEdges(face, geometry); gmsh::vectorpair outDimTags; gmsh::model::occ::importShapesNativePointer( @@ -744,9 +792,12 @@ double IncrementalMeshTools::estimateMeshSize(const GeometryData& geometry) return 10.0; } -std::size_t IncrementalMeshTools::faceCount(const GmshIncrementalMeshState& state) +std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) { - return state.meshContext ? state.meshContext->faceCount() : 0; + if (!geometry.cad_index.built) + return 0; + return static_cast( + geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].Extent()); } std::size_t IncrementalMeshTools::meshedEdgeCount(const GmshIncrementalMeshState& state) @@ -817,6 +868,7 @@ bool IncrementalMeshTools::writeMeshObj( bool IncrementalMeshTools::deleteFaceMesh( MeshData& mesh_data, + GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex) @@ -829,18 +881,16 @@ bool IncrementalMeshTools::deleteFaceMesh( } // 释放面的边界边(更新引用计数并在归零时移除) - if (state.meshContext) { - TopoDS_Face face = state.meshContext->getFaceByIndex(faceIndex); - auto occEdges = state.meshContext->getFaceEdgeIds(face); - for (GeomEdgeId globalEdgeId : occEdges) { - auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); - if (refIt != state.meshedEdgeRefCounts.end()) { - refIt->second--; - if (refIt->second <= 0) { - state.meshedEdgeRefCounts.erase(refIt); - state.meshedEdgesCache.erase(globalEdgeId); - spdlog::info("Edge {} cache cleaned up.", globalEdgeId); - } + TopoDS_Face face = getFaceByIndex( + geometry, model_layer.geomRegistry(), faceIndex); + for (GeomEdgeId globalEdgeId : getFaceEdgeIds(face, geometry)) { + auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); + if (refIt != state.meshedEdgeRefCounts.end()) { + refIt->second--; + if (refIt->second <= 0) { + state.meshedEdgeRefCounts.erase(refIt); + state.meshedEdgesCache.erase(globalEdgeId); + spdlog::info("Edge {} cache cleaned up.", globalEdgeId); } } } @@ -895,7 +945,7 @@ SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( { if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) { spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); - deleteFaceMesh(mesh_data, state, model_layer, faceIndex); + deleteFaceMesh(mesh_data, geometry, state, model_layer, faceIndex); } return meshSingleFace( mesh_data, geometry, state, model_layer, faceIndex, meshSize, meshTypeIndex); diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 5041490..0027fd5 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -39,13 +39,14 @@ SingleFaceMeshResult remeshSingleFace( bool deleteFaceMesh( MeshData& mesh_data, + GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex); double estimateMeshSize(const GeometryData& geometry); -std::size_t faceCount(const GmshIncrementalMeshState& state); +std::size_t faceCount(const GeometryData& geometry); std::size_t meshedEdgeCount(const GmshIncrementalMeshState& state); diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index cdc3e7f..aa440ca 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -1,7 +1,6 @@ #include #include "GeometryData.h" -#include "IncrementalMeshContext.h" #include "IncrementalMeshTools.h" #include "MeshData.h" #include "ModelLayer.h" @@ -91,11 +90,8 @@ static void resetGeneratedMesh(AppContext& ctx) { ctx.meshData.init(); ctx.gmshState.clear(); - if (ctx.geometry.rootShape) { + if (ctx.geometry.rootShape) ctx.geometry.ensureCadIndexBuilt(ctx.modelLayer.geomRegistry()); - ctx.gmshState.meshContext = - std::make_unique(ctx.geometry, ctx.modelLayer.geomRegistry()); - } ctx.currentIndex = 0; ctx.meshSize = IncrementalMeshTools::estimateMeshSize(ctx.geometry); reloadPolyData(ctx); @@ -170,7 +166,7 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, auto* interactor = static_cast(caller); std::string key = interactor->GetKeySym(); - std::size_t total = IncrementalMeshTools::faceCount(ctx->gmshState); + std::size_t total = IncrementalMeshTools::faceCount(ctx->geometry); if (key == "space") { if (ctx->currentIndex >= total) { @@ -208,7 +204,9 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, return; } ctx->currentIndex--; - if (IncrementalMeshTools::deleteFaceMesh(ctx->meshData, ctx->gmshState, ctx->modelLayer, ctx->currentIndex)) { + if (IncrementalMeshTools::deleteFaceMesh( + ctx->meshData, ctx->geometry, ctx->gmshState, + ctx->modelLayer, ctx->currentIndex)) { reloadPolyData(*ctx); } } else if (key == "h" || key == "H") { -- Gitee From afb48e1e9e29f7f512f8199d2ea6f5f96acd6c99 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Wed, 24 Jun 2026 22:29:16 +0800 Subject: [PATCH 12/21] =?UTF-8?q?feat(gmsh=EF=BC=89:=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/SideBar.qml | 1 + plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 138 +++++++++++++----- plugins/algo/GmshPlugin/GmshMeshOptions.h | 77 ++++++++++ plugins/algo/GmshPlugin/GmshMeshTypes.h | 8 - .../algo/GmshPlugin/IncrementalMeshTools.cpp | 92 +++++++----- .../algo/GmshPlugin/IncrementalMeshTools.h | 23 ++- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 11 +- 7 files changed, 267 insertions(+), 83 deletions(-) create mode 100644 plugins/algo/GmshPlugin/GmshMeshOptions.h delete mode 100644 plugins/algo/GmshPlugin/GmshMeshTypes.h diff --git a/app/SideBar.qml b/app/SideBar.qml index 57ec7c0..87f71f2 100644 --- a/app/SideBar.qml +++ b/app/SideBar.qml @@ -142,6 +142,7 @@ Item{ comboModel.append({"text":items[i]}) } } + root.parameters[index] = value = parameterComboBox.currentIndex } } } diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 82e5117..45979dc 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -3,6 +3,7 @@ #include "ArgObject.h" #include "ComponentData.h" #include "ComponentOperator.h" +#include "GmshMeshOptions.h" #include "GeometryData.h" #include "IncrementalMeshTools.h" #include "MeshData.h" @@ -13,18 +14,68 @@ #include #include +#include #include using core::ArgType; +namespace { + +// 从 Text 参数读取可选 double;空字符串表示用户不设置,后续使用默认值。 +std::optional readOptionalDouble(const std::vector& args, std::size_t index) +{ + if (args.size() <= index) + return std::nullopt; + + const std::string* text = args[index].get(); + if (!text || text->empty()) + return std::nullopt; + + try { + return std::stod(*text); + } catch (...) { + spdlog::warn("GmshMesh: invalid double parameter {} = '{}'", index, *text); + return std::nullopt; + } +} + +// 从 Text 参数读取可选 int;空字符串表示用户不设置。 +std::optional readOptionalInt(const std::vector& args, std::size_t index) +{ + if (args.size() <= index) + return std::nullopt; + + const std::string* text = args[index].get(); + if (!text || text->empty()) + return std::nullopt; + + try { + return std::stoi(*text); + } catch (...) { + spdlog::warn("GmshMesh: invalid int parameter {} = '{}'", index, *text); + return std::nullopt; + } +} + +// Combo 参数没有“空值”,所以第一个选项统一作为“默认”。 +int readComboIndex(const std::vector& args, std::size_t index, int defaultValue = 0) +{ + if (args.size() <= index) + return defaultValue; + + const int* value = args[index].get(); + return value ? *value : defaultValue; +} + +} // namespace + std::any systems::algo::GmshMeshHandler::execute( HandlerContext& context, const std::vector& args) { int faceIndex = -1; - double meshSize = 0.0; int operationMode = 1; - int meshTypeIndex = 0; + IncrementalMeshTools::GmshMeshParameters parameters; if (args.size() >= 1) { const std::string* idxStr = args[0].get(); @@ -37,32 +88,40 @@ std::any systems::algo::GmshMeshHandler::execute( } } - if (args.size() >= 2) { - const std::string* sizeStr = args[1].get(); - if (sizeStr && !sizeStr->empty()) { - try { - meshSize = std::stod(*sizeStr); - } catch (...) { - spdlog::warn("GmshMesh: invalid size '{}'", *sizeStr); + if (args.size() <= 4) { + // 兼容旧参数顺序:面索引、网格尺寸、操作模式、网格类型。 + parameters.targetMeshSize = readOptionalDouble(args, 1).value_or(0.0); + if (args.size() >= 3) { + const std::string* opStr = args[2].get(); + if (opStr && !opStr->empty()) { + try { + operationMode = std::stoi(*opStr); + } catch (...) { + spdlog::warn("GmshMesh: invalid operation mode '{}', using default 1 (Mesh)", *opStr); + } } } - } - - if (args.size() >= 3) { - const std::string* opStr = args[2].get(); - if (opStr && !opStr->empty()) { - try { - operationMode = std::stoi(*opStr); - } catch (...) { - spdlog::warn("GmshMesh: invalid operation mode '{}', using default 1 (Mesh)", *opStr); - } + if (args.size() >= 4) { + const int* value = args[3].get(); + if (value) + parameters.meshTypeIndex = *value; } - } - - if (args.size() >= 4) { - const int* value = args[3].get(); - if (value) - meshTypeIndex = *value; + } else { + operationMode = readComboIndex(args, 1) + 1; + parameters.targetMeshSize = readOptionalDouble(args, 2).value_or(0.0); + parameters.minMeshSize = readOptionalDouble(args, 3).value_or(0.0); + parameters.maxMeshSize = readOptionalDouble(args, 4).value_or(0.0); + parameters.meshAlgorithm = gmshComboValue( + kGmshMeshAlgorithmComboValues, readComboIndex(args, 5)); + parameters.meshTypeIndex = readComboIndex(args, 6); + parameters.algorithmSwitchOnFailure = 0; + parameters.smoothingSteps = readOptionalInt(args, 7).value_or(0); + parameters.recombineAlgorithm = gmshComboValue( + kGmshRecombinationAlgorithmComboValues, readComboIndex(args, 8)); + parameters.recombineAngle = readOptionalDouble(args, 9).value_or(45.0); + parameters.quadMinQuality = readOptionalDouble(args, 10).value_or(0.0); + parameters.recombineOptimizeTopology = readOptionalInt(args, 11).value_or(0); + parameters.structuredEdgeDivisions = readOptionalInt(args, 12).value_or(0); } if (faceIndex < 0) { @@ -100,8 +159,8 @@ std::any systems::algo::GmshMeshHandler::execute( return {}; } - if (meshSize <= 0.0) - meshSize = IncrementalMeshTools::estimateMeshSize(*geometry); + if (parameters.targetMeshSize <= 0.0) + parameters.targetMeshSize = IncrementalMeshTools::estimateMeshSize(*geometry); std::size_t faceKey = static_cast(faceIndex); SingleFaceMeshResult result; @@ -111,18 +170,18 @@ std::any systems::algo::GmshMeshHandler::execute( spdlog::info("GmshMesh: face {} already meshed, skip mesh mode; use remesh mode to rebuild", faceIndex); return {}; } - spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, meshSize); + spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, parameters.targetMeshSize); result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, meshSize, meshTypeIndex); + *meshData, *geometry, state, modelLayer, faceKey, parameters.targetMeshSize, parameters); } else if (operationMode == 2) { spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); if (IncrementalMeshTools::deleteFaceMesh( *meshData, *geometry, state, modelLayer, faceKey)) spdlog::info("GmshMesh: delete face {} success", faceIndex); } else if (operationMode == 3) { - spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, meshSize); + spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, parameters.targetMeshSize); result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, meshSize, meshTypeIndex); + *meshData, *geometry, state, modelLayer, faceKey, parameters.targetMeshSize, parameters); } else { spdlog::warn("GmshMesh: unknown operation mode {}, skip", operationMode); return {}; @@ -173,9 +232,18 @@ void systems::algo::GmshMeshHandler::removeExpiredStates(const ModelLayer& model std::vector systems::algo::GmshMeshHandler::args_type() const { return { - ArgType { ArgTypeEnum::Text, "面索引(0开始)", "" }, - ArgType { ArgTypeEnum::Text, "网格尺寸(留空自动)", "" }, - ArgType { ArgTypeEnum::Text, "1: mesh 2: delete 3: remesh", "" }, - ArgType { ArgTypeEnum::Combo, "网格类型", "三角形,四边形主导,结构化四边形" } + ArgType { ArgTypeEnum::Text, "CAD面索引(0开始)", "" }, + ArgType { ArgTypeEnum::Combo, "操作模式", "划分,删除,重划分" }, + ArgType { ArgTypeEnum::Text, "目标网格尺寸(留空自动)", "" }, + ArgType { ArgTypeEnum::Text, "最小网格尺寸(留空默认)", "" }, + ArgType { ArgTypeEnum::Text, "最大网格尺寸(留空默认)", "" }, + ArgType { ArgTypeEnum::Combo, "二维网格算法", kGmshMeshAlgorithmComboText }, + ArgType { ArgTypeEnum::Combo, "网格类型", kGmshSurfaceMeshTypeComboText }, + ArgType { ArgTypeEnum::Text, "平滑次数(留空默认)", "" }, + ArgType { ArgTypeEnum::Combo, "四边形重组算法", kGmshRecombinationAlgorithmComboText }, + ArgType { ArgTypeEnum::Text, "重组角度阈值(默认45)", "" }, + ArgType { ArgTypeEnum::Text, "四边形最低质量(0~1,留空默认)", "" }, + ArgType { ArgTypeEnum::Text, "拓扑优化次数(留空默认)", "" }, + ArgType { ArgTypeEnum::Text, "结构化网格边划分数(留空自动)", "" } }; } diff --git a/plugins/algo/GmshPlugin/GmshMeshOptions.h b/plugins/algo/GmshPlugin/GmshMeshOptions.h new file mode 100644 index 0000000..05a7f58 --- /dev/null +++ b/plugins/algo/GmshPlugin/GmshMeshOptions.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +// Gmsh 曲面网格类型。数值需要和前端“网格类型” Combo 的选项顺序一致。 +enum class GmshSurfaceMeshType { + Triangle = 0, + QuadDominant = 1, + StructuredQuadrilateral = 2 +}; + +// Gmsh 二维网格算法,对应 Mesh.Algorithm 的真实取值。 +enum class GmshMeshAlgorithm { + MeshAdapt = 1, + Automatic = 2, + Delaunay = 5, + FrontalDelaunay = 6, + Bamg = 7, + FrontalDelaunayForQuads = 8, + PackingParallelograms = 9, + QuasiStructuredQuad = 11 +}; + +// Gmsh 四边形重组算法,对应 Mesh.RecombinationAlgorithm 的真实取值。 +enum class GmshRecombinationAlgorithm { + UseGmshDefault = -1, + Simple = 0, + Blossom = 1, + SimpleFullQuad = 2, + BlossomFullQuad = 3 +}; + +// 前端 Combo 文案和下面的取值数组必须保持同一顺序。 +inline constexpr const char* kGmshSurfaceMeshTypeComboText = + "三角形,四边形主导,结构化四边形"; + +inline constexpr const char* kGmshMeshAlgorithmComboText = + "默认(Frontal-Delaunay),MeshAdapt,Automatic,Delaunay," + "BAMG,Frontal-Delaunay for Quads," + "Packing Parallelograms,Quasi-structured Quad"; + +inline constexpr std::array kGmshMeshAlgorithmComboValues { + static_cast(GmshMeshAlgorithm::FrontalDelaunay), + static_cast(GmshMeshAlgorithm::MeshAdapt), + static_cast(GmshMeshAlgorithm::Automatic), + static_cast(GmshMeshAlgorithm::Delaunay), + static_cast(GmshMeshAlgorithm::Bamg), + static_cast(GmshMeshAlgorithm::FrontalDelaunayForQuads), + static_cast(GmshMeshAlgorithm::PackingParallelograms), + static_cast(GmshMeshAlgorithm::QuasiStructuredQuad) +}; + +inline constexpr const char* kGmshRecombinationAlgorithmComboText = + "默认,Simple,Blossom,Simple full-quad,Blossom full-quad"; + +inline constexpr std::array kGmshRecombinationAlgorithmComboValues { + static_cast(GmshRecombinationAlgorithm::UseGmshDefault), + static_cast(GmshRecombinationAlgorithm::Simple), + static_cast(GmshRecombinationAlgorithm::Blossom), + static_cast(GmshRecombinationAlgorithm::SimpleFullQuad), + static_cast(GmshRecombinationAlgorithm::BlossomFullQuad) +}; + +// 将 Combo 索引转换为 Gmsh option 的真实取值;索引异常时使用第 0 项默认值。 +template +int gmshComboValue(const std::array& values, int comboIndex) +{ + if (comboIndex < 0) + return values[0]; + + std::size_t index = static_cast(comboIndex); + if (index >= values.size()) + return values[0]; + + return values[index]; +} diff --git a/plugins/algo/GmshPlugin/GmshMeshTypes.h b/plugins/algo/GmshPlugin/GmshMeshTypes.h deleted file mode 100644 index af0ca8e..0000000 --- a/plugins/algo/GmshPlugin/GmshMeshTypes.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -// Gmsh 曲面网格类型。数值需要和前端 Combo 的选项顺序保持一致。 -enum class GmshSurfaceMeshType { - Triangle = 0, - QuadDominant = 1, - StructuredQuadrilateral = 2 -}; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index fb97093..24aa471 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -1,17 +1,15 @@ #include "IncrementalMeshTools.h" #include "GeometryRegistry.h" -#include "GmshMeshTypes.h" +#include "GmshMeshOptions.h" #include "MeshData.h" #include "ModelLayer.h" #include -#include #include #include #include #include -#include #include #include #include @@ -120,16 +118,6 @@ TopoDS_Shape loadStep(const std::string& path) return reader.OneShape(); } -// ---- 包 Compound ---- -TopoDS_Compound makeFaceCompound(const TopoDS_Face& face) -{ - BRep_Builder builder; - TopoDS_Compound compound; - builder.MakeCompound(compound); - builder.Add(compound, face); - return compound; -} - // 返回 GeometryData 中指定索引的 CAD 面,实际 Shape 统一从 GeometryRegistry 取得。 TopoDS_Face getFaceByIndex( const GeometryData& geometry, @@ -242,9 +230,45 @@ struct EdgeTransfiniteInfo { bool fixedByExistingMesh {}; }; -// 根据曲线长度和目标网格尺寸估算节点数,并延续原实现的偶数节点约束。 -int estimateEdgePointCount(int gmshTag, double meshSize) +// 设置 Gmsh 数值 option; +void setGmshNumberOption(const std::string& name, double value) +{ + try { + gmsh::option::setNumber(name, value); + } catch (const std::exception& e) { + spdlog::warn("GmshMesh: cannot set option {} = {}: {}", name, value, e.what()); + } +} + +// 把用户参数转换为 Gmsh 全局网格选项。只在这里直接写 Gmsh option,便于后续维护默认值。 +void configureMeshingOptions(const IncrementalMeshTools::GmshMeshParameters& parameters, double meshSize) +{ + double minSize = parameters.minMeshSize > 0.0 ? parameters.minMeshSize : meshSize * 0.5; + double maxSize = parameters.maxMeshSize > 0.0 ? parameters.maxMeshSize : meshSize; + + setGmshNumberOption("Mesh.MeshSizeMin", minSize); + setGmshNumberOption("Mesh.MeshSizeMax", maxSize); + setGmshNumberOption("Mesh.MeshOnlyEmpty", 1); + setGmshNumberOption("Mesh.SaveAll", 1); + setGmshNumberOption("Mesh.Algorithm", parameters.meshAlgorithm); + setGmshNumberOption("Mesh.RecombineAll", 0); + setGmshNumberOption("Mesh.Smoothing", parameters.smoothingSteps); + + setGmshNumberOption("Mesh.AlgorithmSwitchOnFailure", parameters.algorithmSwitchOnFailure); + if (parameters.recombineAlgorithm >= 0) + setGmshNumberOption("Mesh.RecombinationAlgorithm", parameters.recombineAlgorithm); + if (parameters.quadMinQuality > 0.0) + setGmshNumberOption("Mesh.RecombineMinimumQuality", parameters.quadMinQuality); + if (parameters.recombineOptimizeTopology > 0) + setGmshNumberOption("Mesh.RecombineOptimizeTopology", parameters.recombineOptimizeTopology); +} + +// 根据曲线长度和目标网格尺寸估算结构化曲线节点数;或者指定的划分段数 +int estimateEdgePointCount(int gmshTag, double meshSize, int structuredEdgeDivisions) { + if (structuredEdgeDivisions > 0) + return std::max(2, structuredEdgeDivisions ); + double length = 0.0; gmsh::model::occ::getMass(1, gmshTag, length); @@ -261,7 +285,8 @@ EdgeTransfiniteInfo makeEdgeTransfiniteInfo( int gmshTag, const std::map& gmshToOcc, const GmshIncrementalMeshState& state, - double meshSize) + double meshSize, + int structuredEdgeDivisions) { EdgeTransfiniteInfo info; info.gmshTag = gmshTag; @@ -276,7 +301,7 @@ EdgeTransfiniteInfo makeEdgeTransfiniteInfo( } } - info.pointCount = estimateEdgePointCount(gmshTag, meshSize); + info.pointCount = estimateEdgePointCount(gmshTag, meshSize, structuredEdgeDivisions); return info; } @@ -320,13 +345,14 @@ bool configureSurfaceMeshType( GmshSurfaceMeshType meshType, const std::map& gmshToOcc, const GmshIncrementalMeshState& state, - double meshSize) + double meshSize, + const IncrementalMeshTools::GmshMeshParameters& parameters) { if (meshType == GmshSurfaceMeshType::Triangle) return true; if (meshType == GmshSurfaceMeshType::QuadDominant) { - gmsh::model::mesh::setRecombine(2, faceTag, 45.0); + gmsh::model::mesh::setRecombine(2, faceTag, parameters.recombineAngle); return true; } @@ -346,7 +372,8 @@ bool configureSurfaceMeshType( std::array edges {}; for (std::size_t i = 0; i < edges.size(); ++i) - edges[i] = makeEdgeTransfiniteInfo(edgeTags[i], gmshToOcc, state, meshSize); + edges[i] = makeEdgeTransfiniteInfo( + edgeTags[i], gmshToOcc, state, meshSize, parameters.structuredEdgeDivisions); int firstPairPointCount = 0; int secondPairPointCount = 0; @@ -360,7 +387,7 @@ bool configureSurfaceMeshType( gmsh::model::mesh::setTransfiniteCurve(edges[1].gmshTag, secondPairPointCount); gmsh::model::mesh::setTransfiniteCurve(edges[3].gmshTag, secondPairPointCount); gmsh::model::mesh::setTransfiniteSurface(faceTag); - gmsh::model::mesh::setRecombine(2, faceTag, 45.0); + gmsh::model::mesh::setRecombine(2, faceTag, parameters.recombineAngle); return true; } @@ -663,10 +690,10 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( ModelLayer& model_layer, std::size_t faceIndex, double meshSize, - int meshTypeIndex) + const GmshMeshParameters& parameters) { SingleFaceMeshResult result; - GmshSurfaceMeshType meshType = parseSurfaceMeshType(meshTypeIndex); + GmshSurfaceMeshType meshType = parseSurfaceMeshType(parameters.meshTypeIndex); std::size_t totalFaces = IncrementalMeshTools::faceCount(geometry); if (faceIndex >= totalFaces) { @@ -684,10 +711,8 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( spdlog::error("Face {} is null or invalid", faceIndex); return result; } - TopoDS_Compound compound = makeFaceCompound(face); - gmsh::initialize(); - gmsh::option::setNumber("General.Terminal", 1); + setGmshNumberOption("General.Terminal", 1); gmsh::model::add("face_model"); // 先逐边导入并记录返回 tag,再导入整个面;面导入会复用相同 OCC 边的 tag。 @@ -695,7 +720,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::vectorpair outDimTags; gmsh::model::occ::importShapesNativePointer( - static_cast(&compound), outDimTags); + static_cast(&face), outDimTags); gmsh::model::occ::synchronize(); std::vector> faceDimTags; @@ -722,15 +747,10 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } spdlog::info(" {} shared, {} free", shared, free); - gmsh::option::setNumber("Mesh.MeshSizeMin", meshSize * 0.5); - gmsh::option::setNumber("Mesh.MeshSizeMax", meshSize); - gmsh::option::setNumber("Mesh.MeshOnlyEmpty", 1); - gmsh::option::setNumber("Mesh.SaveAll", 1); - gmsh::option::setNumber("Mesh.Algorithm", 6); - gmsh::option::setNumber("Mesh.RecombineAll", 0); + configureMeshingOptions(parameters, meshSize); try { - if (!configureSurfaceMeshType(faceTag, meshType, gmshToOcc, state, meshSize)) { + if (!configureSurfaceMeshType(faceTag, meshType, gmshToOcc, state, meshSize, parameters)) { spdlog::warn(" Cannot configure {} mesh", surfaceMeshTypeName(meshType)); storeNewEdges(state, gmshToOcc); gmsh::finalize(); @@ -941,12 +961,12 @@ SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( ModelLayer& model_layer, std::size_t faceIndex, double meshSize, - int meshTypeIndex) + const GmshMeshParameters& parameters) { if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) { spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); deleteFaceMesh(mesh_data, geometry, state, model_layer, faceIndex); } return meshSingleFace( - mesh_data, geometry, state, model_layer, faceIndex, meshSize, meshTypeIndex); + mesh_data, geometry, state, model_layer, faceIndex, meshSize, parameters); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 0027fd5..9e304d3 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -13,6 +13,22 @@ class GeometryRegistry; namespace IncrementalMeshTools { +// Gmsh 单面划分参数。空白 UI 参数在 Handler 层会被转换为这里的默认值。 +struct GmshMeshParameters { + double targetMeshSize {}; + double minMeshSize {}; + double maxMeshSize {}; + int meshAlgorithm { 6 }; + int meshTypeIndex {}; + int algorithmSwitchOnFailure {}; + int smoothingSteps {}; + int recombineAlgorithm { -1 }; + double recombineAngle { 45.0 }; + double quadMinQuality {}; + int recombineOptimizeTopology {}; + int structuredEdgeDivisions {}; +}; + bool initMeshing( const std::string& stepFile, GeometryData& geometry, @@ -26,16 +42,17 @@ SingleFaceMeshResult meshSingleFace( ModelLayer& model_layer, std::size_t faceIndex, double meshSize, - int meshTypeIndex = 0); + const GmshMeshParameters& parameters); + SingleFaceMeshResult remeshSingleFace( - MeshData& mesh_data, + MeshData& mesh_data, GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, std::size_t faceIndex, double meshSize, - int meshTypeIndex = 0); + const GmshMeshParameters& parameters); bool deleteFaceMesh( MeshData& mesh_data, diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index aa440ca..d90a699 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -174,8 +174,17 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, return; } + IncrementalMeshTools::GmshMeshParameters parameters; + parameters.targetMeshSize = ctx->meshSize; + auto result = IncrementalMeshTools::meshSingleFace( - ctx->meshData, ctx->geometry, ctx->gmshState, ctx->modelLayer, ctx->currentIndex, ctx->meshSize); + ctx->meshData, + ctx->geometry, + ctx->gmshState, + ctx->modelLayer, + ctx->currentIndex, + ctx->meshSize, + parameters); ctx->currentIndex++; if (result.success) { -- Gitee From 5cfdeb9d26d1ff9152558ac69c1d389987dc6c3d Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Thu, 25 Jun 2026 13:34:42 +0800 Subject: [PATCH 13/21] =?UTF-8?q?reactor(gmsh):=E6=8C=89=E7=85=A7=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=8F=90=E4=BA=A4=E4=BF=AE=E6=94=B9=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=92=8C=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 1 - .../GmshPlugin/GmshIncrementalMeshState.cpp | 12 ---- .../GmshPlugin/GmshIncrementalMeshState.h | 10 --- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 4 +- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 68 +++++++------------ .../algo/GmshPlugin/IncrementalMeshTools.h | 4 +- .../algo/GmshPlugin/test/GmshPluginTest.cpp | 13 ++-- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 28 ++++---- 8 files changed, 52 insertions(+), 88 deletions(-) delete mode 100644 plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index 9867564..a21243d 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -3,7 +3,6 @@ find_package(gmsh REQUIRED) precess_add_algo_plugin(GmshPlugin SOURCES "GmshMeshHandler.cpp" - "GmshIncrementalMeshState.cpp" "IncrementalMeshTools.cpp" PLUGIN_H "GmshMeshPlugin.h" ) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp deleted file mode 100644 index c5b2721..0000000 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "GmshIncrementalMeshState.h" - -GmshIncrementalMeshState::GmshIncrementalMeshState() = default; -GmshIncrementalMeshState::~GmshIncrementalMeshState() = default; - -void GmshIncrementalMeshState::clear() -{ - meshedEdgesCache.clear(); - meshedFacesCache.clear(); - meshedEdgeRefCounts.clear(); - local_to_global_point_ids.clear(); -} diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h index 0673e46..fdc9a55 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -10,9 +10,6 @@ struct MeshedEdgeData { std::vector coords; std::vector paramCoords; - - std::size_t nodeCount() const { return coords.size() / 3; } - bool empty() const { return coords.empty(); } }; // 保存单个 CAD 面的网格结果,面删除和重划分时用它重建整体 MeshData。 @@ -25,16 +22,9 @@ struct SingleFaceMeshResult { // 保存一个 component 的 Gmsh 增量网格状态,由 Gmsh 插件管理生命周期。 struct GmshIncrementalMeshState { - GmshIncrementalMeshState(); - ~GmshIncrementalMeshState(); - // 已划分 CAD 边的节点缓存,key 使用 geometry 的全局 CAD 边 ID。 std::map meshedEdgesCache; std::map meshedFacesCache; // 已划分 CAD 边被多少个面复用,用于删除面网格时判断是否清理边缓存。 std::map meshedEdgeRefCounts; - std::vector local_to_global_point_ids; - - // 清空当前 component 的增量网格缓存。 - void clear(); }; diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 45979dc..6a38ad7 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -151,7 +151,7 @@ std::any systems::algo::GmshMeshHandler::execute( GmshIncrementalMeshState& state = component_states_[context.cur_component.componentId()]; - geometry->ensureCadIndexBuilt(modelLayer.geomRegistry()); + geometry->ensureIndexBuilt(modelLayer.geomRegistry()); std::size_t totalFaces = IncrementalMeshTools::faceCount(*geometry); if (static_cast(faceIndex) >= totalFaces) { spdlog::error("GmshMesh: face id {} out of range (total {} face)", @@ -208,7 +208,7 @@ std::any systems::algo::GmshMeshHandler::execute( } std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeMeshObj(*meshData, state, meshOut)) { + if (!IncrementalMeshTools::writeMeshObj(*meshData, meshOut)) { spdlog::error("GmshMesh: cant save meshdata"); return {}; } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 24aa471..4b3e4c6 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -32,21 +32,17 @@ class TempNodeLookup { public: explicit TempNodeLookup( MeshData& mesh_data, - GmshIncrementalMeshState& state, ModelLayer& model_layer, double tolerance = 1e-7) : _mesh_data(mesh_data) - , _state(state) , _model_layer(model_layer) , _tolerance(tolerance) { const auto& vertices = _mesh_data.vertex_positions_; - const auto& global_ids = _state.local_to_global_point_ids; - for (size_t i = 0; i < vertices.size(); ++i) { + const auto& global_ids = _mesh_data.local_to_global_; + for (size_t i = 0; i < vertices.size() && i < global_ids.size(); ++i) { auto qc = _quantize(vertices[i][0], vertices[i][1], vertices[i][2]); - if (i < global_ids.size()) { - _map[qc] = global_ids[i]; - } + _map[qc] = global_ids[i]; } } @@ -59,13 +55,9 @@ public: std::array point { x, y, z }; Index global_id = _model_layer.appendGlobalPoints({ point }); - if (_mesh_data.global_point_base_ < 0) { - _mesh_data.global_point_base_ = global_id; - } _mesh_data.vertex_positions_.push_back(point); _mesh_data.vertex_count_ = static_cast(_mesh_data.vertex_positions_.size()); - _mesh_data.point_ids_are_global_ = true; - _state.local_to_global_point_ids.push_back(global_id); + _mesh_data.local_to_global_.push_back(global_id); _map[qc] = global_id; return global_id; } @@ -100,7 +92,6 @@ private: } MeshData& _mesh_data; - GmshIncrementalMeshState& _state; ModelLayer& _model_layer; double _tolerance; std::unordered_map _map; @@ -125,12 +116,12 @@ TopoDS_Face getFaceByIndex( std::size_t faceIndex) { const auto& faceMap = - geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)]; + geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)]; int localFaceId = static_cast(faceIndex) + 1; if (localFaceId < 1 || localFaceId > faceMap.Extent()) return {}; - GeomFaceId globalFaceId = geometry.cad_index.faceGlobalId(localFaceId); + GeomFaceId globalFaceId = geometry.index.faceGlobalId(localFaceId); const TopoDS_Shape* shape = registry.getFace(globalFaceId); return shape ? TopoDS::Face(*shape) : TopoDS_Face {}; } @@ -141,12 +132,12 @@ std::vector getFaceEdgeIds( const GeometryData& geometry) { const auto& edgeMap = - geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; + geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; std::vector edgeIds; for (TopExp_Explorer explorer(face, TopAbs_EDGE); explorer.More(); explorer.Next()) { int localEdgeId = edgeMap.FindIndex(explorer.Current()); - GeomEdgeId globalEdgeId = geometry.cad_index.edgeGlobalId(localEdgeId); + GeomEdgeId globalEdgeId = geometry.index.edgeGlobalId(localEdgeId); if (globalEdgeId != kInvalidGeomEdgeId && std::find(edgeIds.begin(), edgeIds.end(), globalEdgeId) == edgeIds.end()) { edgeIds.push_back(globalEdgeId); @@ -184,13 +175,13 @@ std::map importGmshEdges(const TopoDS_Face& face, { std::map result; const auto& edgeMap = - geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; + geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)]; std::vector importedEdgeIds; for (TopExp_Explorer explorer(face, TopAbs_EDGE); explorer.More(); explorer.Next()) { TopoDS_Edge edge = TopoDS::Edge(explorer.Current()); int localEdgeId = edgeMap.FindIndex(edge); - GeomEdgeId globalEdgeId = geometry.cad_index.edgeGlobalId(localEdgeId); + GeomEdgeId globalEdgeId = geometry.index.edgeGlobalId(localEdgeId); if (globalEdgeId == kInvalidGeomEdgeId || std::find(importedEdgeIds.begin(), importedEdgeIds.end(), globalEdgeId) != importedEdgeIds.end()) { continue; @@ -294,8 +285,8 @@ EdgeTransfiniteInfo makeEdgeTransfiniteInfo( auto occIt = gmshToOcc.find(gmshTag); if (occIt != gmshToOcc.end()) { auto cacheIt = state.meshedEdgesCache.find(occIt->second); - if (cacheIt != state.meshedEdgesCache.end() && !cacheIt->second.empty()) { - info.pointCount = static_cast(cacheIt->second.nodeCount()); + if (cacheIt != state.meshedEdgesCache.end() && !cacheIt->second.coords.empty()) { + info.pointCount = static_cast(cacheIt->second.coords.size() / 3); info.fixedByExistingMesh = true; return info; } @@ -398,9 +389,9 @@ bool injectConstrainedEdge(int gmshTag, std::size_t& elemCounter, std::map& vtxNodeMap) { - if (nodes.empty()) + if (nodes.coords.empty()) return false; - std::size_t nc = nodes.nodeCount(); + std::size_t nc = nodes.coords.size() / 3; std::vector> vtxBnd; gmsh::model::getBoundary({ { 1, gmshTag } }, vtxBnd, false, false, false); @@ -606,7 +597,7 @@ void storeNewEdges(GmshIncrementalMeshState& state, const std::map localToGlobal(result.vertices.size()); for (size_t i = 0; i < result.vertices.size(); ++i) { @@ -665,19 +655,19 @@ bool IncrementalMeshTools::initMeshing(const std::string& stepFile, GmshIncrementalMeshState& state, GeometryRegistry& registry) { - state.clear(); - if (geometry.cad_index.built) - geometry.cad_index.release(registry); + state = {}; + if (geometry.index.built) + geometry.index.release(registry); geometry.rootShape = std::make_unique(loadStep(stepFile)); if (geometry.rootShape->IsNull()) { spdlog::error("Failed to load: {}", stepFile); return false; } - geometry.ensureCadIndexBuilt(registry); + geometry.ensureIndexBuilt(registry); std::size_t faceCount = IncrementalMeshTools::faceCount(geometry); std::size_t edgeCount = static_cast( - geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)].Extent()); + geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)].Extent()); spdlog::info("Loaded: {} faces, {} global edges", faceCount, edgeCount); return faceCount > 0; @@ -788,7 +778,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( state.meshedFacesCache[faceIndex] = result; // 缓存面结果 if (result.success) { - mergeMeshResult(mesh_data, state, model_layer, result); + mergeMeshResult(mesh_data, model_layer, result); } gmsh::finalize(); return result; @@ -814,15 +804,10 @@ double IncrementalMeshTools::estimateMeshSize(const GeometryData& geometry) std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) { - if (!geometry.cad_index.built) + if (!geometry.index.built) return 0; return static_cast( - geometry.cad_index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].Extent()); -} - -std::size_t IncrementalMeshTools::meshedEdgeCount(const GmshIncrementalMeshState& state) -{ - return state.meshedEdgeRefCounts.size(); + geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].Extent()); } bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath) @@ -850,7 +835,6 @@ bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, c bool IncrementalMeshTools::writeMeshObj( const MeshData& res, - const GmshIncrementalMeshState& state, const std::filesystem::path& filepath) { std::ofstream ofs(filepath); @@ -864,7 +848,7 @@ bool IncrementalMeshTools::writeMeshObj( ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; std::unordered_map globalToLocal; - const auto& globalIds = state.local_to_global_point_ids; + const auto& globalIds = res.local_to_global_; for (std::size_t i = 0; i < globalIds.size(); ++i) { globalToLocal[globalIds[i]] = i + 1; } @@ -922,7 +906,7 @@ bool IncrementalMeshTools::deleteFaceMesh( mesh_data.face_vertices_offset_.clear(); mesh_data.face_vertices_offset_.push_back(0); - TempNodeLookup lookup(mesh_data, state, model_layer, 1e-7); + TempNodeLookup lookup(mesh_data, model_layer, 1e-7); // 遍历目前还保留的所有有效面,按序装入MeshData for (const auto& [fIdx, faceMeshResult] : state.meshedFacesCache) { diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 9e304d3..8a555f3 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -65,10 +65,8 @@ double estimateMeshSize(const GeometryData& geometry); std::size_t faceCount(const GeometryData& geometry); -std::size_t meshedEdgeCount(const GmshIncrementalMeshState& state); - bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); -bool writeMeshObj(const MeshData& res, const GmshIncrementalMeshState& state, const std::filesystem::path& filepath); +bool writeMeshObj(const MeshData& res, const std::filesystem::path& filepath); } // namespace IncrementalMeshTools diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index 12c6b3c..c8e2729 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -5,7 +5,6 @@ #include "ArgObject.h" #include "ComponentData.h" #include "GeometryData.h" -#include "ModelData.h" #include "ModelIOSystemBase.h" #include "ModelLayer.h" @@ -51,9 +50,15 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") auto geometryData = std::make_unique(); geometryData->rootShape = std::make_unique(boxMaker.Shape()); + ComponentDatas components; + auto component = std::make_unique(); + component->name = "box"; + component->geometry = std::move(geometryData); + components.push_back(std::move(component)); + ModelLayer modelLayer; - Index modelId = modelLayer.addModel(std::make_unique(std::move(geometryData))); - auto componentIds = modelLayer.getComponentIds(modelId); + Index modelId = modelLayer.addModel("box", std::move(components)); + auto componentIds = modelLayer.modelById(modelId)->componentIds(); REQUIRE(componentIds.size() == 1); auto componentOp = modelLayer.getComponentOperator(componentIds[0]); @@ -73,7 +78,7 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") REQUIRE(comp != nullptr); REQUIRE(comp->mesh != nullptr); REQUIRE(comp->geometry != nullptr); - REQUIRE_FALSE(comp->mesh->vertex_positions_.empty()); + REQUIRE_FALSE(comp->mesh->local_to_global_.empty()); REQUIRE_FALSE(comp->mesh->face_vertices_.empty()); REQUIRE(comp->mesh->face_vertices_offset_.size() > 1); } diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index d90a699..941f55e 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -26,7 +26,7 @@ #include #include -// Runtime state shared by the VTK key callback and the example main loop. +// VTK 按键回调和示例主循环共享的运行状态。 struct AppContext { GeometryData geometry; GmshIncrementalMeshState gmshState; @@ -38,18 +38,18 @@ struct AppContext { double meshSize { 10.0 }; }; -// Builds a local file/render index for mesh vertices whose faces store global point ids. -static std::unordered_map buildGlobalToLocalPointMap(const GmshIncrementalMeshState& state) +// 根据当前 MeshData 的局部到全局点映射,生成“全局点 ID -> 本地点序号”的查询表。 +static std::unordered_map buildGlobalToLocalPointMap(const MeshData& mesh) { std::unordered_map globalToLocal; - const auto& globalIds = state.local_to_global_point_ids; + const auto& globalIds = mesh.local_to_global_; for (std::size_t i = 0; i < globalIds.size(); ++i) { globalToLocal[globalIds[i]] = i; } return globalToLocal; } -// Reloads vtkPolyData from MeshData without depending on app/render classes. +// 从 MeshData 重建 vtkPolyData,避免示例程序依赖 app/render 层。 static void reloadPolyData(AppContext& ctx) { auto points = vtkSmartPointer::New(); @@ -58,7 +58,7 @@ static void reloadPolyData(AppContext& ctx) } auto polys = vtkSmartPointer::New(); - auto globalToLocal = buildGlobalToLocalPointMap(ctx.gmshState); + auto globalToLocal = buildGlobalToLocalPointMap(ctx.meshData); for (std::size_t i = 0; i + 1 < ctx.meshData.face_vertices_offset_.size(); ++i) { std::size_t begin = ctx.meshData.face_vertices_offset_[i]; @@ -85,20 +85,20 @@ static void reloadPolyData(AppContext& ctx) ctx.window->Render(); } -// Resets generated mesh and Gmsh caches while keeping the loaded CAD shape. +// 清空已生成网格和 Gmsh 缓存,但保留已经加载的 CAD 形体。 static void resetGeneratedMesh(AppContext& ctx) { ctx.meshData.init(); - ctx.gmshState.clear(); + ctx.gmshState = {}; if (ctx.geometry.rootShape) - ctx.geometry.ensureCadIndexBuilt(ctx.modelLayer.geomRegistry()); + ctx.geometry.ensureIndexBuilt(ctx.modelLayer.geomRegistry()); ctx.currentIndex = 0; ctx.meshSize = IncrementalMeshTools::estimateMeshSize(ctx.geometry); reloadPolyData(ctx); } -// Saves the example mesh by translating global point ids back to local file node ids. -static void saveMesh(const MeshData& mesh, const GmshIncrementalMeshState& state, const std::string& filename) +// 保存示例网格时,把全局点 ID 转回文件内的局部点编号。 +static void saveMesh(const MeshData& mesh, const std::string& filename) { if (mesh.vertex_positions_.empty()) { spdlog::warn("No mesh data to save."); @@ -112,7 +112,7 @@ static void saveMesh(const MeshData& mesh, const GmshIncrementalMeshState& state std::vector nodeTags(mesh.vertex_positions_.size()); std::vector nodeCoords(mesh.vertex_positions_.size() * 3); - auto globalToLocal = buildGlobalToLocalPointMap(state); + auto globalToLocal = buildGlobalToLocalPointMap(mesh); for (std::size_t i = 0; i < mesh.vertex_positions_.size(); ++i) { nodeTags[i] = i + 1; @@ -193,11 +193,11 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, ctx->meshSize *= 1.5; spdlog::info("nodes={}, cached_edges={}, next_size={:.4f}", ctx->meshData.vertex_positions_.size(), - IncrementalMeshTools::meshedEdgeCount(ctx->gmshState), + ctx->gmshState.meshedEdgeRefCounts.size(), ctx->meshSize); } } else if (key == "s" || key == "S") { - saveMesh(ctx->meshData, ctx->gmshState, "final_mesh.msh"); + saveMesh(ctx->meshData, "final_mesh.msh"); } else if (key == "r" || key == "R") { resetGeneratedMesh(*ctx); spdlog::info("Reset mesh, size={:.4f}", ctx->meshSize); -- Gitee From e99f36f31a484df638eca89ebf407ca27dbd5c99 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Thu, 25 Jun 2026 17:03:48 +0800 Subject: [PATCH 14/21] =?UTF-8?q?refactor(gmsh)=EF=BC=9A=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=92=8C=E8=B0=83=E7=94=A8=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?io?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 24 ++----- plugins/algo/GmshPlugin/GmshMeshOptions.h | 36 +++++++--- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 71 ++----------------- .../algo/GmshPlugin/IncrementalMeshTools.h | 6 +- 4 files changed, 38 insertions(+), 99 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 6a38ad7..f4ea8a9 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -118,10 +118,8 @@ std::any systems::algo::GmshMeshHandler::execute( parameters.smoothingSteps = readOptionalInt(args, 7).value_or(0); parameters.recombineAlgorithm = gmshComboValue( kGmshRecombinationAlgorithmComboValues, readComboIndex(args, 8)); - parameters.recombineAngle = readOptionalDouble(args, 9).value_or(45.0); - parameters.quadMinQuality = readOptionalDouble(args, 10).value_or(0.0); - parameters.recombineOptimizeTopology = readOptionalInt(args, 11).value_or(0); - parameters.structuredEdgeDivisions = readOptionalInt(args, 12).value_or(0); + parameters.quadMinQuality = readOptionalDouble(args, 9).value_or(0.0); + parameters.structuredEdgeDivisions = readOptionalInt(args, 10).value_or(0); } if (faceIndex < 0) { @@ -198,20 +196,14 @@ std::any systems::algo::GmshMeshHandler::execute( result.vertices.size(), result.face_vertices_offset.size() - 1, state.meshedEdgeRefCounts.size()); - - // std::string faceOut = core::TempFile::instance().path().string() + "_single_face_" + std::to_string(faceIndex) + ".obj"; - // if (!IncrementalMeshTools::writeSingleFaceObj(result, faceOut)) { - // spdlog::error("GmshMesh: cant save single face"); - // return {}; - // } - // context.io_system.read(faceOut, "Wavefront .obj file", {}); } std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; - if (!IncrementalMeshTools::writeMeshObj(*meshData, meshOut)) { - spdlog::error("GmshMesh: cant save meshdata"); - return {}; - } + context.io_system.writeComponents( + { context.cur_component.componentId() }, + meshOut, + "Wavefront .obj file", + {}); context.io_system.read(meshOut, "Wavefront .obj file", {}); context.cur_component.notifyChanged(); @@ -241,9 +233,7 @@ std::vector systems::algo::GmshMeshHandler::args_type() const ArgType { ArgTypeEnum::Combo, "网格类型", kGmshSurfaceMeshTypeComboText }, ArgType { ArgTypeEnum::Text, "平滑次数(留空默认)", "" }, ArgType { ArgTypeEnum::Combo, "四边形重组算法", kGmshRecombinationAlgorithmComboText }, - ArgType { ArgTypeEnum::Text, "重组角度阈值(默认45)", "" }, ArgType { ArgTypeEnum::Text, "四边形最低质量(0~1,留空默认)", "" }, - ArgType { ArgTypeEnum::Text, "拓扑优化次数(留空默认)", "" }, ArgType { ArgTypeEnum::Text, "结构化网格边划分数(留空自动)", "" } }; } diff --git a/plugins/algo/GmshPlugin/GmshMeshOptions.h b/plugins/algo/GmshPlugin/GmshMeshOptions.h index 05a7f58..54c7e4e 100644 --- a/plugins/algo/GmshPlugin/GmshMeshOptions.h +++ b/plugins/algo/GmshPlugin/GmshMeshOptions.h @@ -3,31 +3,47 @@ #include #include -// Gmsh 曲面网格类型。数值需要和前端“网格类型” Combo 的选项顺序一致。 +// Gmsh 曲面网格类型。数值需要和前端Combo 的选项顺序一致。 enum class GmshSurfaceMeshType { + // 0:普通三角形网格,不启用四边形重组或结构化约束。 Triangle = 0, + // 1:四边形主导网格,先生成二维网格,再通过 Recombine 尽量重组成四边形。 QuadDominant = 1, + // 2:结构化四边形网格,使用 Transfinite Curve/Surface 约束生成行列结构。 StructuredQuadrilateral = 2 }; -// Gmsh 二维网格算法,对应 Mesh.Algorithm 的真实取值。 +// Gmsh 二维曲面网格算法,对应 Mesh.Algorithm 的官方取值。 enum class GmshMeshAlgorithm { + // 1:MeshAdapt。基于局部网格修改,复杂曲面上通常更鲁棒。 MeshAdapt = 1, + // 2:Automatic。Gmsh 自动选择;平面通常用 Delaunay,非平面通常用 MeshAdapt。 Automatic = 2, + // 3:Initial mesh only。只生成初始网格,主要用于调试,暂不暴露到 UI。 + InitialMeshOnly = 3, + // 5:Delaunay。平面大网格速度较快,对复杂尺寸场也比较适合。 Delaunay = 5, + // 6:Frontal-Delaunay。Gmsh 二维算法默认值,通常能得到较好的三角形质量。 FrontalDelaunay = 6, + // 7:BAMG。偏各向异性三角网格;当前插件没有专门的各向异性参数。 Bamg = 7, + // 8:Frontal-Delaunay for Quads。生成更适合后续四边形重组的三角网格。 FrontalDelaunayForQuads = 8, + // 9:Packing of Parallelograms。四边形相关算法,适用范围比默认算法更窄。 PackingParallelograms = 9, + // 11:Quasi-structured Quad。准结构化四边形算法;暂不暴露到 UI,避免和结构化四边形混淆。 QuasiStructuredQuad = 11 }; // Gmsh 四边形重组算法,对应 Mesh.RecombinationAlgorithm 的真实取值。 enum class GmshRecombinationAlgorithm { - UseGmshDefault = -1, + // 0:Simple。简单重组算法。 Simple = 0, + // 1:Blossom。Gmsh 默认重组算法,通常质量和成功率更均衡。 Blossom = 1, + // 2:Simple full-quad。尝试生成全四边形网格。 SimpleFullQuad = 2, + // 3:Blossom full-quad。基于 Blossom 的全四边形重组。 BlossomFullQuad = 3 }; @@ -37,25 +53,25 @@ inline constexpr const char* kGmshSurfaceMeshTypeComboText = inline constexpr const char* kGmshMeshAlgorithmComboText = "默认(Frontal-Delaunay),MeshAdapt,Automatic,Delaunay," - "BAMG,Frontal-Delaunay for Quads," - "Packing Parallelograms,Quasi-structured Quad"; + "Frontal-Delaunay,BAMG,Frontal-Delaunay for Quads," + "Packing Parallelograms"; -inline constexpr std::array kGmshMeshAlgorithmComboValues { +inline constexpr std::array kGmshMeshAlgorithmComboValues { static_cast(GmshMeshAlgorithm::FrontalDelaunay), static_cast(GmshMeshAlgorithm::MeshAdapt), static_cast(GmshMeshAlgorithm::Automatic), static_cast(GmshMeshAlgorithm::Delaunay), + static_cast(GmshMeshAlgorithm::FrontalDelaunay), static_cast(GmshMeshAlgorithm::Bamg), static_cast(GmshMeshAlgorithm::FrontalDelaunayForQuads), - static_cast(GmshMeshAlgorithm::PackingParallelograms), - static_cast(GmshMeshAlgorithm::QuasiStructuredQuad) + static_cast(GmshMeshAlgorithm::PackingParallelograms) }; inline constexpr const char* kGmshRecombinationAlgorithmComboText = - "默认,Simple,Blossom,Simple full-quad,Blossom full-quad"; + "默认(Blossom),Simple,Blossom,Simple full-quad,Blossom full-quad"; inline constexpr std::array kGmshRecombinationAlgorithmComboValues { - static_cast(GmshRecombinationAlgorithm::UseGmshDefault), + static_cast(GmshRecombinationAlgorithm::Blossom), static_cast(GmshRecombinationAlgorithm::Simple), static_cast(GmshRecombinationAlgorithm::Blossom), static_cast(GmshRecombinationAlgorithm::SimpleFullQuad), diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 4b3e4c6..1740958 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -246,19 +246,16 @@ void configureMeshingOptions(const IncrementalMeshTools::GmshMeshParameters& par setGmshNumberOption("Mesh.Smoothing", parameters.smoothingSteps); setGmshNumberOption("Mesh.AlgorithmSwitchOnFailure", parameters.algorithmSwitchOnFailure); - if (parameters.recombineAlgorithm >= 0) - setGmshNumberOption("Mesh.RecombinationAlgorithm", parameters.recombineAlgorithm); + setGmshNumberOption("Mesh.RecombinationAlgorithm", parameters.recombineAlgorithm); if (parameters.quadMinQuality > 0.0) setGmshNumberOption("Mesh.RecombineMinimumQuality", parameters.quadMinQuality); - if (parameters.recombineOptimizeTopology > 0) - setGmshNumberOption("Mesh.RecombineOptimizeTopology", parameters.recombineOptimizeTopology); } // 根据曲线长度和目标网格尺寸估算结构化曲线节点数;或者指定的划分段数 int estimateEdgePointCount(int gmshTag, double meshSize, int structuredEdgeDivisions) { if (structuredEdgeDivisions > 0) - return std::max(2, structuredEdgeDivisions ); + return std::max(2, structuredEdgeDivisions + 1); double length = 0.0; gmsh::model::occ::getMass(1, gmshTag, length); @@ -343,7 +340,7 @@ bool configureSurfaceMeshType( return true; if (meshType == GmshSurfaceMeshType::QuadDominant) { - gmsh::model::mesh::setRecombine(2, faceTag, parameters.recombineAngle); + gmsh::model::mesh::setRecombine(2, faceTag, 45.0); return true; } @@ -378,7 +375,7 @@ bool configureSurfaceMeshType( gmsh::model::mesh::setTransfiniteCurve(edges[1].gmshTag, secondPairPointCount); gmsh::model::mesh::setTransfiniteCurve(edges[3].gmshTag, secondPairPointCount); gmsh::model::mesh::setTransfiniteSurface(faceTag); - gmsh::model::mesh::setRecombine(2, faceTag, parameters.recombineAngle); + gmsh::model::mesh::setRecombine(2, faceTag, 45.0); return true; } @@ -810,66 +807,6 @@ std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].Extent()); } -bool IncrementalMeshTools::writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath) -{ - if (!res.success) - return false; - - std::ofstream ofs(filepath); - if (!ofs.is_open()) - return false; - - for (const auto& v : res.vertices) - ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; - - for (size_t i = 0; i + 1 < res.face_vertices_offset.size(); ++i) { - size_t start = res.face_vertices_offset[i]; - size_t end = res.face_vertices_offset[i + 1]; - ofs << "f"; - for (size_t j = start; j < end; ++j) - ofs << " " << (res.face_vertices[j] + 1); - ofs << "\n"; - } - return true; -} - -bool IncrementalMeshTools::writeMeshObj( - const MeshData& res, - const std::filesystem::path& filepath) -{ - std::ofstream ofs(filepath); - if (!ofs.is_open()) - return false; - - ofs << "o GmshMergedMesh\n"; - ofs << "g 0\n"; - - for (const auto& v : res.vertex_positions_) - ofs << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; - - std::unordered_map globalToLocal; - const auto& globalIds = res.local_to_global_; - for (std::size_t i = 0; i < globalIds.size(); ++i) { - globalToLocal[globalIds[i]] = i + 1; - } - - for (size_t i = 0; i + 1 < res.face_vertices_offset_.size(); ++i) { - size_t start = res.face_vertices_offset_[i]; - size_t end = res.face_vertices_offset_[i + 1]; - ofs << "f"; - for (size_t j = start; j < end; ++j) { - auto it = globalToLocal.find(res.face_vertices_[j]); - if (it == globalToLocal.end()) { - spdlog::error("GmshMesh: missing local point for global id {}", res.face_vertices_[j]); - return false; - } - ofs << " " << it->second; - } - ofs << "\n"; - } - return true; -} - bool IncrementalMeshTools::deleteFaceMesh( MeshData& mesh_data, GeometryData& geometry, diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 8a555f3..8fc474a 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -22,10 +22,8 @@ struct GmshMeshParameters { int meshTypeIndex {}; int algorithmSwitchOnFailure {}; int smoothingSteps {}; - int recombineAlgorithm { -1 }; - double recombineAngle { 45.0 }; + int recombineAlgorithm { 1 }; double quadMinQuality {}; - int recombineOptimizeTopology {}; int structuredEdgeDivisions {}; }; @@ -67,6 +65,4 @@ std::size_t faceCount(const GeometryData& geometry); bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); -bool writeMeshObj(const MeshData& res, const std::filesystem::path& filepath); - } // namespace IncrementalMeshTools -- Gitee From 2f7c3a9aba5be1ba5c70a86eb2634447b46dccbd Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Mon, 29 Jun 2026 16:51:02 +0800 Subject: [PATCH 15/21] =?UTF-8?q?refactor(gmsh)=EF=BC=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E6=98=AF=E5=90=A6=E5=86=99=E5=87=BA=E7=BD=91?= =?UTF-8?q?=E6=A0=BC=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index f4ea8a9..97d875a 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -67,6 +67,16 @@ int readComboIndex(const std::vector& args, std::size_t index, return value ? *value : defaultValue; } +// 从 Bool 参数读取开关; +bool readBoolArg(const std::vector& args, std::size_t index, bool defaultValue) +{ + if (args.size() <= index) + return defaultValue; + + const bool* value = args[index].get(); + return value ? *value : defaultValue; +} + } // namespace std::any systems::algo::GmshMeshHandler::execute( @@ -121,6 +131,7 @@ std::any systems::algo::GmshMeshHandler::execute( parameters.quadMinQuality = readOptionalDouble(args, 9).value_or(0.0); parameters.structuredEdgeDivisions = readOptionalInt(args, 10).value_or(0); } + bool writeModel = readBoolArg(args, 11, true); if (faceIndex < 0) { spdlog::error("GmshMesh: need face id"); @@ -198,13 +209,15 @@ std::any systems::algo::GmshMeshHandler::execute( state.meshedEdgeRefCounts.size()); } - std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; - context.io_system.writeComponents( - { context.cur_component.componentId() }, - meshOut, - "Wavefront .obj file", - {}); - context.io_system.read(meshOut, "Wavefront .obj file", {}); + if (writeModel) { + std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; + context.io_system.writeComponents( + { context.cur_component.componentId() }, + meshOut, + "Wavefront .obj file", + {}); + context.io_system.read(meshOut, "Wavefront .obj file", {}); + } context.cur_component.notifyChanged(); return {}; @@ -234,6 +247,7 @@ std::vector systems::algo::GmshMeshHandler::args_type() const ArgType { ArgTypeEnum::Text, "平滑次数(留空默认)", "" }, ArgType { ArgTypeEnum::Combo, "四边形重组算法", kGmshRecombinationAlgorithmComboText }, ArgType { ArgTypeEnum::Text, "四边形最低质量(0~1,留空默认)", "" }, - ArgType { ArgTypeEnum::Text, "结构化网格边划分数(留空自动)", "" } + ArgType { ArgTypeEnum::Text, "结构化网格边划分数(留空自动)", "" }, + ArgType { ArgTypeEnum::Bool, "是否写出网格", "true" } }; } -- Gitee From da0ace5dc144d618fba0335674b5cf8378ebef47 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Mon, 29 Jun 2026 16:51:41 +0800 Subject: [PATCH 16/21] =?UTF-8?q?feat(sideBar)=EF=BC=9A=E5=9C=A8SidBar?= =?UTF-8?q?=E9=87=8C=E5=8A=A0=E4=BA=86=E4=B8=80=E4=B8=AAbool=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20=E6=8F=92=E4=BB=B6=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/SideBar.qml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/SideBar.qml b/app/SideBar.qml index 87f71f2..b7eaeb2 100644 --- a/app/SideBar.qml +++ b/app/SideBar.qml @@ -72,6 +72,9 @@ Item{ if(model.type === QArgType.Text){ //文字输入框 return textComponent } + if(model.type === QArgType.Bool){ //布尔值 + return boolComponent + } } } } @@ -298,4 +301,32 @@ Item{ } } } + Component{ + id: boolComponent + RowLayout{ + spacing: 5 + width: parameterList.width + + Text{ + id:nametext + text: model.name + } + Rectangle{ + Layout.fillHeight: true + Layout.preferredWidth: 1 + color: "black" + } + CheckBox{ + id: parameterCheckBox + + Component.onCompleted: { + checked = (model.content === "true") + root.parameters[index] = checked + } + onCheckedChanged: { + root.parameters[index] = checked + } + } + } + } } -- Gitee From 2f228d2e1ba7a1c5a558c4b54c457d464429f59e Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sat, 4 Jul 2026 16:51:05 +0800 Subject: [PATCH 17/21] =?UTF-8?q?refactor(gmsh):=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/data/CMakeLists.txt | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/model/data/CMakeLists.txt b/model/data/CMakeLists.txt index 7a3a49b..f56b332 100644 --- a/model/data/CMakeLists.txt +++ b/model/data/CMakeLists.txt @@ -17,24 +17,6 @@ target_link_libraries(Data PUBLIC Core spdlog::spdlog $<$:ws2_32> # spdlog日志库 ) if (OpenCASCADE_FOUND) - if (DEFINED OpenCASCADE_INCLUDE_DIR) - target_include_directories(Data PUBLIC ${OpenCASCADE_INCLUDE_DIR}) - elseif (DEFINED OpenCASCADE_INCLUDE_DIRS) - target_include_directories(Data PUBLIC ${OpenCASCADE_INCLUDE_DIRS}) - else() - message(FATAL_ERROR "OpenCASCADE found but include dir variable not set (OpenCASCADE_INCLUDE_DIR/S not found)") - endif() - - target_link_libraries(Data PRIVATE - TKernel - TKMath - TKG2d - TKG3d - TKGeomBase - TKBRep - TKTopAlgo - ) - target_link_libraries(Data PUBLIC $) target_link_libraries(Data PRIVATE TKernel TKTopAlgo) else() -- Gitee From c09a5c7ac21e7d0206bcf49fc23cc7b65b661217 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sat, 11 Jul 2026 13:33:45 +0800 Subject: [PATCH 18/21] =?UTF-8?q?fix(gmsh):=20=E9=98=B2=E6=AD=A2=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=88=92=E5=88=86=E6=B1=A1=E6=9F=93=E8=BE=B9=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=92=8Cgmsh=E8=B5=84=E6=BA=90=E9=87=8A=E6=94=BE,?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/CMakeLists.txt | 2 - plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 40 ++++++- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 101 +++++++++--------- .../algo/GmshPlugin/IncrementalMeshTools.h | 11 -- plugins/algo/GmshPlugin/test/CMakeLists.txt | 2 + .../algo/GmshPlugin/test/GmshPluginTest.cpp | 45 ++++++++ .../algo/GmshPlugin/test/GmshVtkExample.cpp | 21 +++- 7 files changed, 154 insertions(+), 68 deletions(-) diff --git a/plugins/algo/GmshPlugin/CMakeLists.txt b/plugins/algo/GmshPlugin/CMakeLists.txt index a21243d..b2db17e 100644 --- a/plugins/algo/GmshPlugin/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/CMakeLists.txt @@ -12,8 +12,6 @@ precess_plugin_link_libraries(GmshPlugin TKBRep # BRep_Builder, BRepTools TKernel # Standard_* 基础类型 TKG3d # gp_* 准则基础 - TKDESTEP # STEPControl_Reader - TKXSBase # STEP/IGES 转换基础 TKTopAlgo # TopExp_Explorer ) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 97d875a..79da90f 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -14,7 +14,9 @@ #include #include +#include #include +#include #include using core::ArgType; @@ -32,7 +34,11 @@ std::optional readOptionalDouble(const std::vector& arg return std::nullopt; try { - return std::stod(*text); + std::size_t parsed = 0; + double value = std::stod(*text, &parsed); + if (parsed != text->size() || !std::isfinite(value)) + throw std::invalid_argument("not a finite double"); + return value; } catch (...) { spdlog::warn("GmshMesh: invalid double parameter {} = '{}'", index, *text); return std::nullopt; @@ -50,7 +56,11 @@ std::optional readOptionalInt(const std::vector& args, std return std::nullopt; try { - return std::stoi(*text); + std::size_t parsed = 0; + int value = std::stoi(*text, &parsed); + if (parsed != text->size()) + throw std::invalid_argument("not an integer"); + return value; } catch (...) { spdlog::warn("GmshMesh: invalid int parameter {} = '{}'", index, *text); return std::nullopt; @@ -77,6 +87,29 @@ bool readBoolArg(const std::vector& args, std::size_t index, bo return value ? *value : defaultValue; } +// 校验传入 Gmsh 前的数值参数,避免把不合法尺寸或质量值传递给底层库。 +bool validateMeshingParameters(const IncrementalMeshTools::GmshMeshParameters& parameters) +{ + if (parameters.targetMeshSize < 0.0 + || parameters.minMeshSize < 0.0 + || parameters.maxMeshSize < 0.0 + || parameters.smoothingSteps < 0 + || parameters.structuredEdgeDivisions < 0 + || parameters.quadMinQuality < 0.0 + || parameters.quadMinQuality > 1.0) { + spdlog::error("GmshMesh: invalid meshing parameter range"); + return false; + } + + if (parameters.minMeshSize > 0.0 + && parameters.maxMeshSize > 0.0 + && parameters.minMeshSize > parameters.maxMeshSize) { + spdlog::error("GmshMesh: minimum mesh size must not exceed maximum mesh size"); + return false; + } + return true; +} + } // namespace std::any systems::algo::GmshMeshHandler::execute( @@ -133,6 +166,9 @@ std::any systems::algo::GmshMeshHandler::execute( } bool writeModel = readBoolArg(args, 11, true); + if (!validateMeshingParameters(parameters)) + return {}; + if (faceIndex < 0) { spdlog::error("GmshMesh: need face id"); return {}; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 1740958..1901431 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -7,8 +7,6 @@ #include #include -#include -#include #include #include #include @@ -21,8 +19,9 @@ #include #include #include +#include +#include #include -#include #include namespace { @@ -97,18 +96,6 @@ private: std::unordered_map _map; }; -// ---- 加载 STEP ---- -TopoDS_Shape loadStep(const std::string& path) -{ - STEPControl_Reader reader; - if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) { - spdlog::error("Cannot read STEP: {}", path); - return {}; - } - reader.TransferRoots(); - return reader.OneShape(); -} - // 返回 GeometryData 中指定索引的 CAD 面,实际 Shape 统一从 GeometryRegistry 取得。 TopoDS_Face getFaceByIndex( const GeometryData& geometry, @@ -221,6 +208,37 @@ struct EdgeTransfiniteInfo { bool fixedByExistingMesh {}; }; +// 管理一次单面划分使用的 Gmsh 全局会话,确保异常和提前返回时均会释放资源。 +class GmshSession { +public: + GmshSession() + { + if (gmsh::isInitialized()) + throw std::runtime_error("GmshMesh: Gmsh session is already initialized"); + + try { + gmsh::initialize(); + ownsSession_ = true; + } catch (...) { + if (gmsh::isInitialized()) + gmsh::finalize(); + throw; + } + } + + ~GmshSession() + { + if (ownsSession_ && gmsh::isInitialized()) + gmsh::finalize(); + } + + GmshSession(const GmshSession&) = delete; + GmshSession& operator=(const GmshSession&) = delete; + +private: + bool ownsSession_ {}; +}; + // 设置 Gmsh 数值 option; void setGmshNumberOption(const std::string& name, double value) { @@ -647,29 +665,6 @@ void mergeMeshResult( } // anonymous namespace // 公开接口 -bool IncrementalMeshTools::initMeshing(const std::string& stepFile, - GeometryData& geometry, - GmshIncrementalMeshState& state, - GeometryRegistry& registry) -{ - state = {}; - if (geometry.index.built) - geometry.index.release(registry); - - geometry.rootShape = std::make_unique(loadStep(stepFile)); - if (geometry.rootShape->IsNull()) { - spdlog::error("Failed to load: {}", stepFile); - return false; - } - geometry.ensureIndexBuilt(registry); - std::size_t faceCount = IncrementalMeshTools::faceCount(geometry); - std::size_t edgeCount = static_cast( - geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_EDGE)].Extent()); - spdlog::info("Loaded: {} faces, {} global edges", - faceCount, edgeCount); - return faceCount > 0; -} - SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( MeshData& mesh_data, GeometryData& geometry, @@ -698,7 +693,8 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( spdlog::error("Face {} is null or invalid", faceIndex); return result; } - gmsh::initialize(); + try { + GmshSession session; setGmshNumberOption("General.Terminal", 1); gmsh::model::add("face_model"); @@ -714,7 +710,6 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( gmsh::model::getEntities(faceDimTags, 2); if (faceDimTags.empty()) { spdlog::error(" No face after import"); - gmsh::finalize(); return result; } int faceTag = faceDimTags[0].second; @@ -723,10 +718,14 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( int shared = 0, free = 0; std::map vtxNodeMap; - for (auto& [gt, oid] : gmshToOcc) { - if (state.meshedEdgeRefCounts.count(oid) > 0) { - injectConstrainedEdge(gt, state.meshedEdgesCache.at(oid), - nodeCounter, elemCounter, vtxNodeMap); + for (const auto& [gt, oid] : gmshToOcc) { + auto cacheIt = state.meshedEdgesCache.find(oid); + if (state.meshedEdgeRefCounts.count(oid) > 0 && cacheIt != state.meshedEdgesCache.end()) { + if (!injectConstrainedEdge(gt, cacheIt->second, + nodeCounter, elemCounter, vtxNodeMap)) { + spdlog::error("GmshMesh: failed to inject constrained edge {}", oid); + return result; + } shared++; } else { free++; @@ -739,15 +738,11 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( try { if (!configureSurfaceMeshType(faceTag, meshType, gmshToOcc, state, meshSize, parameters)) { spdlog::warn(" Cannot configure {} mesh", surfaceMeshTypeName(meshType)); - storeNewEdges(state, gmshToOcc); - gmsh::finalize(); return result; } gmsh::model::mesh::generate(2); } catch (const std::exception& e) { spdlog::error(" Mesh failed: {}", e.what()); - storeNewEdges(state, gmshToOcc); - gmsh::finalize(); return result; } @@ -764,20 +759,22 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } if (!has) { spdlog::warn(" No surface elements"); - storeNewEdges(state, gmshToOcc); - gmsh::finalize(); return result; } } result = extractFaceMesh(faceTag); + if (!result.success) + return result; + + // 仅在面网格成功后提交边缓存、引用计数和面缓存,失败路径不改变状态。 storeNewEdges(state, gmshToOcc); state.meshedFacesCache[faceIndex] = result; // 缓存面结果 - if (result.success) { mergeMeshResult(mesh_data, model_layer, result); + } catch (const std::exception& e) { + spdlog::error("GmshMesh: setup or extraction failed: {}", e.what()); } - gmsh::finalize(); return result; } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 8fc474a..957a160 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -1,15 +1,12 @@ #pragma once #include -#include #include -#include #include "GmshIncrementalMeshState.h" #include "GeometryData.h" struct MeshData; class ModelLayer; -class GeometryRegistry; namespace IncrementalMeshTools { @@ -27,12 +24,6 @@ struct GmshMeshParameters { int structuredEdgeDivisions {}; }; -bool initMeshing( - const std::string& stepFile, - GeometryData& geometry, - GmshIncrementalMeshState& state, - GeometryRegistry& registry); - SingleFaceMeshResult meshSingleFace( MeshData& mesh_data, GeometryData& geometry, @@ -63,6 +54,4 @@ double estimateMeshSize(const GeometryData& geometry); std::size_t faceCount(const GeometryData& geometry); -bool writeSingleFaceObj(const SingleFaceMeshResult& res, const std::filesystem::path& filepath); - } // namespace IncrementalMeshTools diff --git a/plugins/algo/GmshPlugin/test/CMakeLists.txt b/plugins/algo/GmshPlugin/test/CMakeLists.txt index c9c027d..50306a5 100644 --- a/plugins/algo/GmshPlugin/test/CMakeLists.txt +++ b/plugins/algo/GmshPlugin/test/CMakeLists.txt @@ -16,6 +16,8 @@ target_link_libraries(GmshVtkExample PRIVATE gmsh::shared Data GmshPluginlib + TKDESTEP + TKXSBase VTK::InteractionStyle VTK::CommonColor VTK::RenderingCore diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index c8e2729..c4c336a 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -82,3 +82,48 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") REQUIRE_FALSE(comp->mesh->face_vertices_.empty()); REQUIRE(comp->mesh->face_vertices_offset_.size() > 1); } + +TEST_CASE("GmshMeshHandler rejects invalid current parameters", "[GmshPlugin]") +{ + BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0); + boxMaker.Build(); + REQUIRE(boxMaker.IsDone()); + + auto geometryData = std::make_unique(); + geometryData->rootShape = std::make_unique(boxMaker.Shape()); + + ComponentDatas components; + auto component = std::make_unique(); + component->geometry = std::move(geometryData); + components.push_back(std::move(component)); + + ModelLayer modelLayer; + Index modelId = modelLayer.addModel("box", std::move(components)); + const Index componentId = modelLayer.modelById(modelId)->componentIds().front(); + auto componentOp = modelLayer.getComponentOperator(componentId); + REQUIRE(componentOp.has_value()); + + MockIOSystem mockIo; + systems::algo::HandlerContext context { mockIo, *componentOp }; + std::vector args { + core::ArgObject::create("0"), + core::ArgObject::create(0), + core::ArgObject::create("1.0"), + core::ArgObject::create("2.0"), + core::ArgObject::create("1.0"), + core::ArgObject::create(0), + core::ArgObject::create(0), + core::ArgObject::create(""), + core::ArgObject::create(0), + core::ArgObject::create(""), + core::ArgObject::create(""), + core::ArgObject::create(false) + }; + + systems::algo::GmshMeshHandler handler; + handler.execute(context, args); + + ComponentData* comp = modelLayer.findComponent(componentId); + REQUIRE(comp != nullptr); + REQUIRE(comp->mesh == nullptr); +} diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index 941f55e..ffae51c 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -7,6 +7,9 @@ #include +#include +#include + #include #include #include @@ -38,6 +41,22 @@ struct AppContext { double meshSize { 10.0 }; }; +// 示例加载 STEP, +static bool loadStepGeometry(const std::string& path, GeometryData& geometry, GeometryRegistry& registry) +{ + STEPControl_Reader reader; + if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) + return false; + + reader.TransferRoots(); + geometry.rootShape = std::make_unique(reader.OneShape()); + if (geometry.rootShape->IsNull()) + return false; + + geometry.ensureIndexBuilt(registry); + return true; +} + // 根据当前 MeshData 的局部到全局点映射,生成“全局点 ID -> 本地点序号”的查询表。 static std::unordered_map buildGlobalToLocalPointMap(const MeshData& mesh) { @@ -234,7 +253,7 @@ int main(int argc, char* argv[]) ctx.polyData = vtkSmartPointer::New(); ctx.meshData.init(); - if (!IncrementalMeshTools::initMeshing(path, ctx.geometry, ctx.gmshState, ctx.modelLayer.geomRegistry())) { + if (!loadStepGeometry(path, ctx.geometry, ctx.modelLayer.geomRegistry())) { spdlog::error("Cannot import: {}", path); return 1; } -- Gitee From 1a3f89b7ed50e2933cede52874941d2ca45d98fd Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sat, 11 Jul 2026 14:50:54 +0800 Subject: [PATCH 19/21] =?UTF-8?q?fix(gmsh):=20=E5=88=A0=E9=99=A4=E9=9D=A2?= =?UTF-8?q?=E7=BD=91=E6=A0=BC=E6=97=B6=E4=BF=9D=E7=95=99=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E7=BD=91=E6=A0=BC=E5=8D=95=E5=85=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GmshPlugin/GmshIncrementalMeshState.h | 2 + .../algo/GmshPlugin/IncrementalMeshTools.cpp | 163 +++++++++++------- .../algo/GmshPlugin/test/GmshPluginTest.cpp | 46 +++++ .../algo/GmshPlugin/test/GmshVtkExample.cpp | 1 + 4 files changed, 146 insertions(+), 66 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h index fdc9a55..62b3400 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -17,6 +17,8 @@ struct SingleFaceMeshResult { std::vector> vertices; std::vector face_vertices; std::vector face_vertices_offset; + // 写入 MeshData 后对应的全局点 ID,用于只删除本次 Gmsh 生成的单元。 + std::vector global_face_vertices; bool success { false }; }; diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index 1901431..ae47907 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -513,7 +514,7 @@ bool injectConstrainedEdge(int gmshTag, gmsh::model::mesh::addNodes(1, gmshTag, innerTags, innerCoords, innerParams); // 线单元 - std::vector allN; +std::vector allN; allN.push_back(tagV0); for (auto t : innerTags) allN.push_back(t); @@ -628,7 +629,7 @@ void storeNewEdges(GmshIncrementalMeshState& state, const std::map(mesh_data.face_vertices_.size())); @@ -662,6 +667,82 @@ void mergeMeshResult( mesh_data.face_vertices_offset_.size() - 1); } +// 从 MeshData 中移除指定 Gmsh 面结果的单元,保留其他算法已写入的单元。 +bool removeCachedFaceCells(MeshData& mesh_data, const SingleFaceMeshResult& result) +{ + if (result.global_face_vertices.size() != result.face_vertices.size() + || result.face_vertices_offset.size() < 2) { + spdlog::error("GmshMesh: cached face topology is incomplete"); + return false; + } + + std::set> targetCells; + for (std::size_t i = 0; i + 1 < result.face_vertices_offset.size(); ++i) { + const std::size_t begin = result.face_vertices_offset[i]; + const std::size_t end = result.face_vertices_offset[i + 1]; + if (begin > end || end > result.global_face_vertices.size()) + return false; + targetCells.emplace(result.global_face_vertices.begin() + begin, + result.global_face_vertices.begin() + end); + } + + std::vector keptVertices; + std::vector keptOffsets { 0 }; + for (std::size_t i = 0; i + 1 < mesh_data.face_vertices_offset_.size(); ++i) { + const Index begin = mesh_data.face_vertices_offset_[i]; + const Index end = mesh_data.face_vertices_offset_[i + 1]; + if (begin < 0 || end < begin + || end > static_cast(mesh_data.face_vertices_.size())) { + spdlog::error("GmshMesh: invalid MeshData face offsets"); + return false; + } + + std::vector cell(mesh_data.face_vertices_.begin() + begin, + mesh_data.face_vertices_.begin() + end); + if (targetCells.erase(cell) == 0) + keptVertices.insert(keptVertices.end(), cell.begin(), cell.end()); + keptOffsets.push_back(static_cast(keptVertices.size())); + } + + if (!targetCells.empty()) { + spdlog::error("GmshMesh: cached face cells are missing from MeshData"); + return false; + } + + mesh_data.face_vertices_ = std::move(keptVertices); + mesh_data.face_vertices_offset_ = std::move(keptOffsets); + return true; +} + +// 释放一个 CAD 面占用的边缓存和面缓存,不修改 MeshData。 +bool releaseFaceCache( + GeometryData& geometry, + GmshIncrementalMeshState& state, + ModelLayer& model_layer, + std::size_t face_index) +{ + auto faceIt = state.meshedFacesCache.find(face_index); + if (faceIt == state.meshedFacesCache.end()) + return false; + + TopoDS_Face face = getFaceByIndex( + geometry, model_layer.geomRegistry(), face_index); + for (GeomEdgeId globalEdgeId : getFaceEdgeIds(face, geometry)) { + auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); + if (refIt == state.meshedEdgeRefCounts.end()) + continue; + + if (--refIt->second <= 0) { + state.meshedEdgeRefCounts.erase(refIt); + state.meshedEdgesCache.erase(globalEdgeId); + } + } + state.meshedFacesCache.erase(faceIt); + return true; +} + +// 复制本次划分会修改的网格数组,用于重划分的候选结果。 +// 提交候选划分产生的网格数组,不影响 MeshData 的属性、边和体数据。 } // anonymous namespace // 公开接口 @@ -697,6 +778,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( GmshSession session; setGmshNumberOption("General.Terminal", 1); gmsh::model::add("face_model"); + auto gmshToOcc = importGmshEdges(face, geometry); // 先逐边导入并记录返回 tag,再导入整个面;面导入会复用相同 OCC 边的 tag。 auto gmshToOcc = importGmshEdges(face, geometry); @@ -764,16 +846,10 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( } result = extractFaceMesh(faceTag); - if (!result.success) - return result; - - // 仅在面网格成功后提交边缓存、引用计数和面缓存,失败路径不改变状态。 - storeNewEdges(state, gmshToOcc); - state.meshedFacesCache[faceIndex] = result; // 缓存面结果 - + if (result.success) { + storeNewEdges(state, gmshToOcc); mergeMeshResult(mesh_data, model_layer, result); - } catch (const std::exception& e) { - spdlog::error("GmshMesh: setup or extraction failed: {}", e.what()); + state.meshedFacesCache[faceIndex] = result; } return result; } @@ -804,6 +880,7 @@ std::size_t IncrementalMeshTools::faceCount(const GeometryData& geometry) geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].Extent()); } + bool IncrementalMeshTools::deleteFaceMesh( MeshData& mesh_data, GeometryData& geometry, @@ -811,63 +888,18 @@ bool IncrementalMeshTools::deleteFaceMesh( ModelLayer& model_layer, std::size_t faceIndex) { - // 检查是否存在该面的缓存 auto it = state.meshedFacesCache.find(faceIndex); if (it == state.meshedFacesCache.end()) { spdlog::warn("Face {} is not meshed or not cached.", faceIndex); return false; } - // 释放面的边界边(更新引用计数并在归零时移除) - TopoDS_Face face = getFaceByIndex( - geometry, model_layer.geomRegistry(), faceIndex); - for (GeomEdgeId globalEdgeId : getFaceEdgeIds(face, geometry)) { - auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); - if (refIt != state.meshedEdgeRefCounts.end()) { - refIt->second--; - if (refIt->second <= 0) { - state.meshedEdgeRefCounts.erase(refIt); - state.meshedEdgesCache.erase(globalEdgeId); - spdlog::info("Edge {} cache cleaned up.", globalEdgeId); - } - } - } - - state.meshedFacesCache.erase(it); - - // 重构meshdata 暂时重建面索引 - mesh_data.face_vertices_.clear(); - mesh_data.face_vertices_offset_.clear(); - mesh_data.face_vertices_offset_.push_back(0); - - TempNodeLookup lookup(mesh_data, model_layer, 1e-7); - - // 遍历目前还保留的所有有效面,按序装入MeshData - for (const auto& [fIdx, faceMeshResult] : state.meshedFacesCache) { - if (!faceMeshResult.success) - continue; - - std::vector localToGlobal(faceMeshResult.vertices.size()); - for (size_t i = 0; i < faceMeshResult.vertices.size(); ++i) { - localToGlobal[i] = lookup.getOrInsert( - faceMeshResult.vertices[i][0], - faceMeshResult.vertices[i][1], - faceMeshResult.vertices[i][2]); - } - - for (size_t i = 0; i + 1 < faceMeshResult.face_vertices_offset.size(); ++i) { - size_t start = faceMeshResult.face_vertices_offset[i]; - size_t end = faceMeshResult.face_vertices_offset[i + 1]; - for (size_t j = start; j < end; ++j) { - mesh_data.face_vertices_.push_back( - localToGlobal[faceMeshResult.face_vertices[j]]); - } - mesh_data.face_vertices_offset_.push_back( - static_cast(mesh_data.face_vertices_.size())); - } - } + if (!removeCachedFaceCells(mesh_data, it->second)) + return false; + if (!releaseFaceCache(geometry, state, model_layer, faceIndex)) + return false; - spdlog::info("Deleted mesh for face {}, rebuilt topology. Remaining cells: {}", + spdlog::info("Deleted Gmsh mesh for face {}. Remaining cells: {}", faceIndex, mesh_data.face_vertices_offset_.size() - 1); return true; } @@ -881,10 +913,9 @@ SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( double meshSize, const GmshMeshParameters& parameters) { - if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) { - spdlog::info("Remeshing: Face {} is already meshed, deleting old mesh first.", faceIndex); + if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) deleteFaceMesh(mesh_data, geometry, state, model_layer, faceIndex); - } + return meshSingleFace( mesh_data, geometry, state, model_layer, faceIndex, meshSize, parameters); } diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index c4c336a..654481d 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -1,6 +1,7 @@ #include #include "GmshMeshHandler.h" +#include "IncrementalMeshTools.h" #include "ArgObject.h" #include "ComponentData.h" @@ -127,3 +128,48 @@ TEST_CASE("GmshMeshHandler rejects invalid current parameters", "[GmshPlugin]") REQUIRE(comp != nullptr); REQUIRE(comp->mesh == nullptr); } + +TEST_CASE("Gmsh delete preserves cells not owned by its face cache", "[GmshPlugin]") +{ + BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0); + boxMaker.Build(); + REQUIRE(boxMaker.IsDone()); + + auto geometryData = std::make_unique(); + geometryData->rootShape = std::make_unique(boxMaker.Shape()); + + ComponentDatas components; + auto component = std::make_unique(); + component->geometry = std::move(geometryData); + component->mesh = std::make_unique(); + component->mesh->init(); + components.push_back(std::move(component)); + + ModelLayer modelLayer; + Index modelId = modelLayer.addModel("box", std::move(components)); + const Index componentId = modelLayer.modelById(modelId)->componentIds().front(); + ComponentData* comp = modelLayer.findComponent(componentId); + REQUIRE(comp != nullptr); + comp->geometry->ensureIndexBuilt(modelLayer.geomRegistry()); + + GmshIncrementalMeshState state; + IncrementalMeshTools::GmshMeshParameters parameters; + parameters.targetMeshSize = 2.0; + auto result = IncrementalMeshTools::meshSingleFace( + *comp->mesh, *comp->geometry, state, modelLayer, 0, 2.0, parameters); + REQUIRE(result.success); + + MeshData& mesh = *comp->mesh; + const Index begin = mesh.face_vertices_offset_[0]; + const Index end = mesh.face_vertices_offset_[1]; + std::vector otherAlgorithmCell( + mesh.face_vertices_.begin() + begin, mesh.face_vertices_.begin() + end); + mesh.face_vertices_.insert(mesh.face_vertices_.end(), + otherAlgorithmCell.begin(), otherAlgorithmCell.end()); + mesh.face_vertices_offset_.push_back(static_cast(mesh.face_vertices_.size())); + + REQUIRE(IncrementalMeshTools::deleteFaceMesh( + mesh, *comp->geometry, state, modelLayer, 0)); + REQUIRE(mesh.face_vertices_offset_.size() == 2); + REQUIRE(mesh.face_vertices_ == otherAlgorithmCell); +} diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index ffae51c..8144d91 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -204,6 +204,7 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, ctx->currentIndex, ctx->meshSize, parameters); + ctx->currentIndex++; if (result.success) { -- Gitee From 1d023dfb831a71ef8cd13451fcb66bdab0bd1f85 Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 12 Jul 2026 16:05:18 +0800 Subject: [PATCH 20/21] =?UTF-8?q?refactor(gmsh)=EF=BC=9A=E5=B0=86=E5=87=A0?= =?UTF-8?q?=E4=BD=95=E9=9D=A2id=E5=8F=82=E6=95=B0=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=87=A0=E4=BD=95=E9=9D=A2=E9=80=89=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GmshPlugin/GmshIncrementalMeshState.h | 3 +- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 79 +++++++++---------- .../algo/GmshPlugin/IncrementalMeshTools.cpp | 65 ++++++--------- .../algo/GmshPlugin/IncrementalMeshTools.h | 6 +- .../algo/GmshPlugin/test/GmshPluginTest.cpp | 71 ++++++----------- .../algo/GmshPlugin/test/GmshVtkExample.cpp | 8 +- 6 files changed, 95 insertions(+), 137 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h index 62b3400..3166da6 100644 --- a/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h +++ b/plugins/algo/GmshPlugin/GmshIncrementalMeshState.h @@ -26,7 +26,8 @@ struct SingleFaceMeshResult { struct GmshIncrementalMeshState { // 已划分 CAD 边的节点缓存,key 使用 geometry 的全局 CAD 边 ID。 std::map meshedEdgesCache; - std::map meshedFacesCache; + // 已划分 CAD 面的网格结果,key 使用 geometry 的全局 CAD 面 ID。 + std::map meshedFacesCache; // 已划分 CAD 边被多少个面复用,用于删除面网格时判断是否清理边缓存。 std::map meshedEdgeRefCounts; }; diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 79da90f..4f08461 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -5,10 +5,13 @@ #include "ComponentOperator.h" #include "GmshMeshOptions.h" #include "GeometryData.h" +#include "GeometryRegistry.h" +#include "GeometrySubshapeIndex.h" #include "IncrementalMeshTools.h" #include "MeshData.h" #include "ModelIOSystemBase.h" #include "ModelLayer.h" +#include "Selection.h" #include "TempFile.h" #include @@ -116,23 +119,11 @@ std::any systems::algo::GmshMeshHandler::execute( HandlerContext& context, const std::vector& args) { - int faceIndex = -1; int operationMode = 1; IncrementalMeshTools::GmshMeshParameters parameters; - if (args.size() >= 1) { - const std::string* idxStr = args[0].get(); - if (idxStr && !idxStr->empty()) { - try { - faceIndex = std::stoi(*idxStr); - } catch (...) { - spdlog::warn("GmshMesh: invalid face index '{}'", *idxStr); - } - } - } - if (args.size() <= 4) { - // 兼容旧参数顺序:面索引、网格尺寸、操作模式、网格类型。 + // 兼容旧参数顺序:选择面、网格尺寸、操作模式、网格类型。 parameters.targetMeshSize = readOptionalDouble(args, 1).value_or(0.0); if (args.size() >= 3) { const std::string* opStr = args[2].get(); @@ -169,11 +160,6 @@ std::any systems::algo::GmshMeshHandler::execute( if (!validateMeshingParameters(parameters)) return {}; - if (faceIndex < 0) { - spdlog::error("GmshMesh: need face id"); - return {}; - } - ComponentData& comp = context.cur_component.component(); ModelLayer& modelLayer = context.cur_component.manager(); removeExpiredStates(modelLayer); @@ -185,6 +171,28 @@ std::any systems::algo::GmshMeshHandler::execute( return {}; } + geometry->ensureIndexBuilt(modelLayer.geomRegistry()); + const auto* selection = args.empty() + ? nullptr + : args[0].get(); + if (!selection || !*selection + || (*selection)->type != ElementEnum::GeometryFace + || (*selection)->ids.size() != 1) { + spdlog::error("GmshMesh: select exactly one geometry face"); + return {}; + } + + const GeomFaceId faceId = (*selection)->ids.front(); + const TopoDS_Shape* selectedFace = modelLayer.geomRegistry().getFace(faceId); + const int localFaceId = selectedFace + ? geometry->index.type_maps[ + GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].FindIndex(*selectedFace) + : 0; + if (localFaceId <= 0) { + spdlog::error("GmshMesh: selected geometry face is not in current component"); + return {}; + } + // 获取或创建当前 component 的 MeshData,Gmsh 生成结果直接写回该 component。 MeshData* meshData = comp.mesh.get(); if (!meshData) { @@ -196,37 +204,28 @@ std::any systems::algo::GmshMeshHandler::execute( GmshIncrementalMeshState& state = component_states_[context.cur_component.componentId()]; - geometry->ensureIndexBuilt(modelLayer.geomRegistry()); - std::size_t totalFaces = IncrementalMeshTools::faceCount(*geometry); - if (static_cast(faceIndex) >= totalFaces) { - spdlog::error("GmshMesh: face id {} out of range (total {} face)", - faceIndex, totalFaces); - return {}; - } - if (parameters.targetMeshSize <= 0.0) parameters.targetMeshSize = IncrementalMeshTools::estimateMeshSize(*geometry); - std::size_t faceKey = static_cast(faceIndex); SingleFaceMeshResult result; if (operationMode == 1) { - if (state.meshedFacesCache.find(faceKey) != state.meshedFacesCache.end()) { - spdlog::info("GmshMesh: face {} already meshed, skip mesh mode; use remesh mode to rebuild", faceIndex); + if (state.meshedFacesCache.find(faceId) != state.meshedFacesCache.end()) { + spdlog::info("GmshMesh: face {} already meshed, skip mesh mode; use remesh mode to rebuild", faceId); return {}; } - spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceIndex, parameters.targetMeshSize); + spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceId, parameters.targetMeshSize); result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, parameters.targetMeshSize, parameters); + *meshData, *geometry, state, modelLayer, faceId, parameters.targetMeshSize, parameters); } else if (operationMode == 2) { - spdlog::info("GmshMesh: delete mesh for face {}", faceIndex); + spdlog::info("GmshMesh: delete mesh for face {}", faceId); if (IncrementalMeshTools::deleteFaceMesh( - *meshData, *geometry, state, modelLayer, faceKey)) - spdlog::info("GmshMesh: delete face {} success", faceIndex); + *meshData, *geometry, state, modelLayer, faceId)) + spdlog::info("GmshMesh: delete face {} success", faceId); } else if (operationMode == 3) { - spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceIndex, parameters.targetMeshSize); + spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceId, parameters.targetMeshSize); result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, state, modelLayer, faceKey, parameters.targetMeshSize, parameters); + *meshData, *geometry, state, modelLayer, faceId, parameters.targetMeshSize, parameters); } else { spdlog::warn("GmshMesh: unknown operation mode {}, skip", operationMode); return {}; @@ -234,19 +233,19 @@ std::any systems::algo::GmshMeshHandler::execute( if (operationMode != 2) { if (!result.success) { - spdlog::warn("GmshMesh: face {} meshing failed", faceIndex); + spdlog::warn("GmshMesh: face {} meshing failed", faceId); return {}; } spdlog::info("GmshMesh: face {} finish, {} nodes, {} cells, cached {} edge", - faceIndex, + faceId, result.vertices.size(), result.face_vertices_offset.size() - 1, state.meshedEdgeRefCounts.size()); } if (writeModel) { - std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceIndex) + ".obj"; + std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceId) + ".obj"; context.io_system.writeComponents( { context.cur_component.componentId() }, meshOut, @@ -273,7 +272,7 @@ void systems::algo::GmshMeshHandler::removeExpiredStates(const ModelLayer& model std::vector systems::algo::GmshMeshHandler::args_type() const { return { - ArgType { ArgTypeEnum::Text, "CAD面索引(0开始)", "" }, + ArgType { ArgTypeEnum::Selector, "选择几何面", "" }, ArgType { ArgTypeEnum::Combo, "操作模式", "划分,删除,重划分" }, ArgType { ArgTypeEnum::Text, "目标网格尺寸(留空自动)", "" }, ArgType { ArgTypeEnum::Text, "最小网格尺寸(留空默认)", "" }, diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp index ae47907..1cf5a62 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.cpp @@ -97,20 +97,12 @@ private: std::unordered_map _map; }; -// 返回 GeometryData 中指定索引的 CAD 面,实际 Shape 统一从 GeometryRegistry 取得。 -TopoDS_Face getFaceByIndex( - const GeometryData& geometry, +// 通过全局几何面 ID 从 GeometryRegistry 取得 CAD 面。 +TopoDS_Face getFaceById( const GeometryRegistry& registry, - std::size_t faceIndex) + GeomFaceId faceId) { - const auto& faceMap = - geometry.index.type_maps[GeometrySubshapeIndex::typeIndex(TopAbs_FACE)]; - int localFaceId = static_cast(faceIndex) + 1; - if (localFaceId < 1 || localFaceId > faceMap.Extent()) - return {}; - - GeomFaceId globalFaceId = geometry.index.faceGlobalId(localFaceId); - const TopoDS_Shape* shape = registry.getFace(globalFaceId); + const TopoDS_Shape* shape = registry.getFace(faceId); return shape ? TopoDS::Face(*shape) : TopoDS_Face {}; } @@ -719,14 +711,13 @@ bool releaseFaceCache( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t face_index) + GeomFaceId faceId) { - auto faceIt = state.meshedFacesCache.find(face_index); + auto faceIt = state.meshedFacesCache.find(faceId); if (faceIt == state.meshedFacesCache.end()) return false; - TopoDS_Face face = getFaceByIndex( - geometry, model_layer.geomRegistry(), face_index); + TopoDS_Face face = getFaceById(model_layer.geomRegistry(), faceId); for (GeomEdgeId globalEdgeId : getFaceEdgeIds(face, geometry)) { auto refIt = state.meshedEdgeRefCounts.find(globalEdgeId); if (refIt == state.meshedEdgeRefCounts.end()) @@ -751,35 +742,25 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex, + GeomFaceId faceId, double meshSize, const GmshMeshParameters& parameters) { SingleFaceMeshResult result; GmshSurfaceMeshType meshType = parseSurfaceMeshType(parameters.meshTypeIndex); - std::size_t totalFaces = IncrementalMeshTools::faceCount(geometry); - if (faceIndex >= totalFaces) { - spdlog::error("faceIndex {} out of range", faceIndex); - return result; - } - - spdlog::info("=== Meshing face {}/{} (size={:.6f}, type={}) ===", - faceIndex + 1, totalFaces, meshSize, + spdlog::info("=== Meshing face {} (size={:.6f}, type={}) ===", + faceId, meshSize, surfaceMeshTypeName(meshType)); - TopoDS_Face face = getFaceByIndex( - geometry, model_layer.geomRegistry(), faceIndex); + TopoDS_Face face = getFaceById(model_layer.geomRegistry(), faceId); if (face.IsNull()) { - spdlog::error("Face {} is null or invalid", faceIndex); + spdlog::error("Face {} is null or invalid", faceId); return result; } - try { - GmshSession session; + GmshSession session; setGmshNumberOption("General.Terminal", 1); gmsh::model::add("face_model"); - auto gmshToOcc = importGmshEdges(face, geometry); - // 先逐边导入并记录返回 tag,再导入整个面;面导入会复用相同 OCC 边的 tag。 auto gmshToOcc = importGmshEdges(face, geometry); @@ -849,7 +830,7 @@ SingleFaceMeshResult IncrementalMeshTools::meshSingleFace( if (result.success) { storeNewEdges(state, gmshToOcc); mergeMeshResult(mesh_data, model_layer, result); - state.meshedFacesCache[faceIndex] = result; + state.meshedFacesCache[faceId] = result; } return result; } @@ -886,21 +867,21 @@ bool IncrementalMeshTools::deleteFaceMesh( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex) + GeomFaceId faceId) { - auto it = state.meshedFacesCache.find(faceIndex); + auto it = state.meshedFacesCache.find(faceId); if (it == state.meshedFacesCache.end()) { - spdlog::warn("Face {} is not meshed or not cached.", faceIndex); + spdlog::warn("Face {} is not meshed or not cached.", faceId); return false; } if (!removeCachedFaceCells(mesh_data, it->second)) return false; - if (!releaseFaceCache(geometry, state, model_layer, faceIndex)) + if (!releaseFaceCache(geometry, state, model_layer, faceId)) return false; spdlog::info("Deleted Gmsh mesh for face {}. Remaining cells: {}", - faceIndex, mesh_data.face_vertices_offset_.size() - 1); + faceId, mesh_data.face_vertices_offset_.size() - 1); return true; } @@ -909,13 +890,13 @@ SingleFaceMeshResult IncrementalMeshTools::remeshSingleFace( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex, + GeomFaceId faceId, double meshSize, const GmshMeshParameters& parameters) { - if (state.meshedFacesCache.find(faceIndex) != state.meshedFacesCache.end()) - deleteFaceMesh(mesh_data, geometry, state, model_layer, faceIndex); + if (state.meshedFacesCache.find(faceId) != state.meshedFacesCache.end()) + deleteFaceMesh(mesh_data, geometry, state, model_layer, faceId); return meshSingleFace( - mesh_data, geometry, state, model_layer, faceIndex, meshSize, parameters); + mesh_data, geometry, state, model_layer, faceId, meshSize, parameters); } diff --git a/plugins/algo/GmshPlugin/IncrementalMeshTools.h b/plugins/algo/GmshPlugin/IncrementalMeshTools.h index 957a160..e7867de 100644 --- a/plugins/algo/GmshPlugin/IncrementalMeshTools.h +++ b/plugins/algo/GmshPlugin/IncrementalMeshTools.h @@ -29,7 +29,7 @@ SingleFaceMeshResult meshSingleFace( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex, + GeomFaceId faceId, double meshSize, const GmshMeshParameters& parameters); @@ -39,7 +39,7 @@ SingleFaceMeshResult remeshSingleFace( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex, + GeomFaceId faceId, double meshSize, const GmshMeshParameters& parameters); @@ -48,7 +48,7 @@ bool deleteFaceMesh( GeometryData& geometry, GmshIncrementalMeshState& state, ModelLayer& model_layer, - std::size_t faceIndex); + GeomFaceId faceId); double estimateMeshSize(const GeometryData& geometry); diff --git a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp index 654481d..1e7009c 100644 --- a/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp +++ b/plugins/algo/GmshPlugin/test/GmshPluginTest.cpp @@ -8,6 +8,7 @@ #include "GeometryData.h" #include "ModelIOSystemBase.h" #include "ModelLayer.h" +#include "Selection.h" #include #include @@ -68,14 +69,24 @@ TEST_CASE("GmshMeshHandler Execution Test", "[GmshPlugin]") MockIOSystem mockIo; systems::algo::HandlerContext context { mockIo, *componentOp }; + ComponentData* comp = modelLayer.findComponent(componentIds[0]); + REQUIRE(comp != nullptr); + comp->geometry->ensureIndexBuilt(modelLayer.geomRegistry()); + auto selection = std::make_shared(); + selection->type = ElementEnum::GeometryFace; + selection->ids = { + comp->geometry->index.faceGlobalId(1), + comp->geometry->index.faceGlobalId(2) + }; + std::vector args; - args.push_back(core::ArgObject::create("0")); + args.push_back(core::ArgObject::create(selection)); args.push_back(core::ArgObject::create("2.0")); systems::algo::GmshMeshHandler handler; handler.execute(context, args); - ComponentData* comp = modelLayer.findComponent(componentIds[0]); + comp = modelLayer.findComponent(componentIds[0]); REQUIRE(comp != nullptr); REQUIRE(comp->mesh != nullptr); REQUIRE(comp->geometry != nullptr); @@ -106,8 +117,15 @@ TEST_CASE("GmshMeshHandler rejects invalid current parameters", "[GmshPlugin]") MockIOSystem mockIo; systems::algo::HandlerContext context { mockIo, *componentOp }; + ComponentData* comp = modelLayer.findComponent(componentId); + REQUIRE(comp != nullptr); + comp->geometry->ensureIndexBuilt(modelLayer.geomRegistry()); + auto selection = std::make_shared(); + selection->type = ElementEnum::GeometryFace; + selection->ids = { comp->geometry->index.faceGlobalId(1) }; + std::vector args { - core::ArgObject::create("0"), + core::ArgObject::create(selection), core::ArgObject::create(0), core::ArgObject::create("1.0"), core::ArgObject::create("2.0"), @@ -124,52 +142,7 @@ TEST_CASE("GmshMeshHandler rejects invalid current parameters", "[GmshPlugin]") systems::algo::GmshMeshHandler handler; handler.execute(context, args); - ComponentData* comp = modelLayer.findComponent(componentId); + comp = modelLayer.findComponent(componentId); REQUIRE(comp != nullptr); REQUIRE(comp->mesh == nullptr); } - -TEST_CASE("Gmsh delete preserves cells not owned by its face cache", "[GmshPlugin]") -{ - BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0); - boxMaker.Build(); - REQUIRE(boxMaker.IsDone()); - - auto geometryData = std::make_unique(); - geometryData->rootShape = std::make_unique(boxMaker.Shape()); - - ComponentDatas components; - auto component = std::make_unique(); - component->geometry = std::move(geometryData); - component->mesh = std::make_unique(); - component->mesh->init(); - components.push_back(std::move(component)); - - ModelLayer modelLayer; - Index modelId = modelLayer.addModel("box", std::move(components)); - const Index componentId = modelLayer.modelById(modelId)->componentIds().front(); - ComponentData* comp = modelLayer.findComponent(componentId); - REQUIRE(comp != nullptr); - comp->geometry->ensureIndexBuilt(modelLayer.geomRegistry()); - - GmshIncrementalMeshState state; - IncrementalMeshTools::GmshMeshParameters parameters; - parameters.targetMeshSize = 2.0; - auto result = IncrementalMeshTools::meshSingleFace( - *comp->mesh, *comp->geometry, state, modelLayer, 0, 2.0, parameters); - REQUIRE(result.success); - - MeshData& mesh = *comp->mesh; - const Index begin = mesh.face_vertices_offset_[0]; - const Index end = mesh.face_vertices_offset_[1]; - std::vector otherAlgorithmCell( - mesh.face_vertices_.begin() + begin, mesh.face_vertices_.begin() + end); - mesh.face_vertices_.insert(mesh.face_vertices_.end(), - otherAlgorithmCell.begin(), otherAlgorithmCell.end()); - mesh.face_vertices_offset_.push_back(static_cast(mesh.face_vertices_.size())); - - REQUIRE(IncrementalMeshTools::deleteFaceMesh( - mesh, *comp->geometry, state, modelLayer, 0)); - REQUIRE(mesh.face_vertices_offset_.size() == 2); - REQUIRE(mesh.face_vertices_ == otherAlgorithmCell); -} diff --git a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp index 8144d91..c15f2bf 100644 --- a/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp +++ b/plugins/algo/GmshPlugin/test/GmshVtkExample.cpp @@ -195,13 +195,15 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, IncrementalMeshTools::GmshMeshParameters parameters; parameters.targetMeshSize = ctx->meshSize; + const GeomFaceId faceId = + ctx->geometry.index.faceGlobalId(static_cast(ctx->currentIndex) + 1); auto result = IncrementalMeshTools::meshSingleFace( ctx->meshData, ctx->geometry, ctx->gmshState, ctx->modelLayer, - ctx->currentIndex, + faceId, ctx->meshSize, parameters); @@ -233,9 +235,11 @@ static void KeyPressCallback(vtkObject* caller, unsigned long, void* clientData, return; } ctx->currentIndex--; + const GeomFaceId faceId = + ctx->geometry.index.faceGlobalId(static_cast(ctx->currentIndex) + 1); if (IncrementalMeshTools::deleteFaceMesh( ctx->meshData, ctx->geometry, ctx->gmshState, - ctx->modelLayer, ctx->currentIndex)) { + ctx->modelLayer, faceId)) { reloadPolyData(*ctx); } } else if (key == "h" || key == "H") { -- Gitee From b67fc50ae9b8af739555accc051677020fb747ce Mon Sep 17 00:00:00 2001 From: Young <2109190674@qq.com> Date: Sun, 12 Jul 2026 18:32:46 +0800 Subject: [PATCH 21/21] =?UTF-8?q?refactor(gmsh)=EF=BC=9A=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E5=A4=9A=E9=80=89=E5=87=A0=E4=BD=95=E9=9D=A2=EF=BC=8C=E5=88=99?= =?UTF-8?q?=E6=8C=89=E5=BA=8F=E5=88=86=E5=88=AB=E5=88=92=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/algo/GmshPlugin/GmshMeshHandler.cpp | 98 ++++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp index 4f08461..24b06f1 100644 --- a/plugins/algo/GmshPlugin/GmshMeshHandler.cpp +++ b/plugins/algo/GmshPlugin/GmshMeshHandler.cpp @@ -177,20 +177,21 @@ std::any systems::algo::GmshMeshHandler::execute( : args[0].get(); if (!selection || !*selection || (*selection)->type != ElementEnum::GeometryFace - || (*selection)->ids.size() != 1) { - spdlog::error("GmshMesh: select exactly one geometry face"); + || (*selection)->ids.empty()) { + spdlog::error("GmshMesh: select at least one geometry face"); return {}; } - const GeomFaceId faceId = (*selection)->ids.front(); - const TopoDS_Shape* selectedFace = modelLayer.geomRegistry().getFace(faceId); - const int localFaceId = selectedFace - ? geometry->index.type_maps[ - GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].FindIndex(*selectedFace) - : 0; - if (localFaceId <= 0) { - spdlog::error("GmshMesh: selected geometry face is not in current component"); - return {}; + for (GeomFaceId faceId : (*selection)->ids) { + const TopoDS_Shape* selectedFace = modelLayer.geomRegistry().getFace(faceId); + const int localFaceId = selectedFace + ? geometry->index.type_maps[ + GeometrySubshapeIndex::typeIndex(TopAbs_FACE)].FindIndex(*selectedFace) + : 0; + if (localFaceId <= 0) { + spdlog::error("GmshMesh: selected geometry face {} is not in current component", faceId); + return {}; + } } // 获取或创建当前 component 的 MeshData,Gmsh 生成结果直接写回该 component。 @@ -207,45 +208,53 @@ std::any systems::algo::GmshMeshHandler::execute( if (parameters.targetMeshSize <= 0.0) parameters.targetMeshSize = IncrementalMeshTools::estimateMeshSize(*geometry); - SingleFaceMeshResult result; - - if (operationMode == 1) { - if (state.meshedFacesCache.find(faceId) != state.meshedFacesCache.end()) { - spdlog::info("GmshMesh: face {} already meshed, skip mesh mode; use remesh mode to rebuild", faceId); - return {}; - } - spdlog::info("GmshMesh: mesh face {} (size={:.4f})", faceId, parameters.targetMeshSize); - result = IncrementalMeshTools::meshSingleFace( - *meshData, *geometry, state, modelLayer, faceId, parameters.targetMeshSize, parameters); - } else if (operationMode == 2) { - spdlog::info("GmshMesh: delete mesh for face {}", faceId); - if (IncrementalMeshTools::deleteFaceMesh( - *meshData, *geometry, state, modelLayer, faceId)) - spdlog::info("GmshMesh: delete face {} success", faceId); - } else if (operationMode == 3) { - spdlog::info("GmshMesh: remesh face {} (size={:.4f})", faceId, parameters.targetMeshSize); - result = IncrementalMeshTools::remeshSingleFace( - *meshData, *geometry, state, modelLayer, faceId, parameters.targetMeshSize, parameters); - } else { + if (operationMode < 1 || operationMode > 3) { spdlog::warn("GmshMesh: unknown operation mode {}, skip", operationMode); return {}; } - if (operationMode != 2) { - if (!result.success) { - spdlog::warn("GmshMesh: face {} meshing failed", faceId); - return {}; - } + std::size_t successCount = 0; + std::size_t failedCount = 0; + for (GeomFaceId faceId : (*selection)->ids) { + if (operationMode == 1) { + if (state.meshedFacesCache.find(faceId) != state.meshedFacesCache.end()) { + spdlog::info("GmshMesh: face {} already meshed, skip mesh mode", faceId); + continue; + } - spdlog::info("GmshMesh: face {} finish, {} nodes, {} cells, cached {} edge", - faceId, - result.vertices.size(), - result.face_vertices_offset.size() - 1, - state.meshedEdgeRefCounts.size()); + auto result = IncrementalMeshTools::meshSingleFace( + *meshData, *geometry, state, modelLayer, + faceId, parameters.targetMeshSize, parameters); + if (!result.success) { + spdlog::warn("GmshMesh: face {} meshing failed", faceId); + ++failedCount; + continue; + } + } else if (operationMode == 2) { + if (!IncrementalMeshTools::deleteFaceMesh( + *meshData, *geometry, state, modelLayer, faceId)) { + spdlog::warn("GmshMesh: delete face {} failed", faceId); + ++failedCount; + continue; + } + } else { + auto result = IncrementalMeshTools::remeshSingleFace( + *meshData, *geometry, state, modelLayer, + faceId, parameters.targetMeshSize, parameters); + if (!result.success) { + spdlog::warn("GmshMesh: face {} remeshing failed", faceId); + ++failedCount; + continue; + } + } + ++successCount; } - if (writeModel) { - std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh_" + std::to_string(faceId) + ".obj"; + spdlog::info("GmshMesh: {} selected faces processed: {} succeeded, {} failed", + (*selection)->ids.size(), successCount, failedCount); + + if (writeModel && successCount > 0) { + std::string meshOut = core::TempFile::instance().path().string() + "_total_mesh.obj"; context.io_system.writeComponents( { context.cur_component.componentId() }, meshOut, @@ -253,7 +262,8 @@ std::any systems::algo::GmshMeshHandler::execute( {}); context.io_system.read(meshOut, "Wavefront .obj file", {}); } - context.cur_component.notifyChanged(); + if (successCount > 0) + context.cur_component.notifyChanged(); return {}; } -- Gitee