news 2026/9/13 13:01:09

OCC+Gmsh+OSG集成:从网格建模到三维可视化的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OCC+Gmsh+OSG集成:从网格建模到三维可视化的工程实践

简介:面向 CAD 领域开发者,这份集成 OCC、Gmsh 与 OSG 的测试程序,把几何建模、网格划分与三维可视化串联为完整工作流,适合需要快速搭建 CAD 原型,或研究三者在 CAE 前处理与后处理中协同工作的工程师。压缩包共 40 个文件,包括 cpp 源码、sln 工程、msh 网格数据、exe 可执行程序,以及 lib/dll 依赖库等,包体约 100.7MB,目录结构清晰,便于按模块查阅。已有 2470 人学习下载。程序演示了读取 Gmsh 网格数据后,通过 OpenCASCADE 完成几何处理,再由 OpenSceneGraph 构建可交互的三维场景,支持旋转、缩放、平移等操作。同时提供 recombine 系列多个阶段的网格文件,涵盖原始、平滑、拓扑处理、质量评估等关键环节,便于对照分析不同处理流程对网格的影响。整个项目可作为 CAD/CAE 工具链二次开发的基础模板,降低多库集成时的配置与接口对接成本,对想深入理解 OCC、Gmsh、OSG 协同机制的开发者尤其有参考价值。

1. 为什么把 OCC、Gmsh、OSG 拧在一起做测试程序

先给结论:OCC 管几何建模,Gmsh 管网格剖分,OSG 管实时渲染,这三者单独拿出来都有大量成熟案例,但真正把它们串成一条完整数据链的工程示例非常少。很多团队做 CAD 相关的预研时,经常卡在"OCC 画完模型导不出 Gmsh 能用的格式,Gmsh 剖完网格 OSG 又读不进去"这样的接口泥潭里。TestProject 这个压缩包的价值不在于它实现了多复杂的算法,而在于它把三条开源技术栈的调用顺序、数据格式转换、渲染节点组织方式固化成了一个可运行的骨架,你拿到手能直接跑,跑通了再往里面填自己的业务逻辑。

这个项目适合三类人:一是刚接触 OCC 与可视化集成的开发者,想用最短路径看到模型从建模到渲染的完整链路;二是做有限元前处理或后处理工具的人,需要把 Gmsh 的 msh 网格数据变成可交互的 3D 场景;三是想评估"OCC + OSG"这套组合能否替代商业 CAD 内核的架构决策者。下文所有分析都以 TestProject 中的文件结构为准,重点拆解 recombine 系列 msh 文件在程序里扮演的角色、OCC 与 Gmsh 数据交换的格式约定,以及 OSG 加载网格时的性能陷阱。

2. Gmsh 网格数据解析:recombine 系列文件的真实含义

2.1 recombine_0before.msh 到 recombine_5final.msh 是什么

TestProject 压缩包里最容易被忽略的是那一串 recombine_**.msh 文件。这不是随机生成的中间产物,而是 Gmsh 执行recombine算法时按顺序落盘的快照。理解这六个文件,就理解了 Gmsh 从原始网格到高质量四边形网格的完整演变过程。

文件名阶段含义网格特征
recombine_0before.msh初始三角剖分结果全部为三角形单元,节点稀疏
recombine_1raw.msh第一次 recombination 原始输出三角形部分合并为四边形,边界未处理
recombine_2smoothed.msh光滑化后的网格节点位置调整,单元质量初步改善
recombine_3topo.msh拓扑修复后消除非流形边,修复悬挂节点
recombine_4quality.msh质量优化后四边形单元的最小角最大化
recombine_5final.msh最终网格满足目标尺寸与质量阈值

在 TestProject.cpp 中,程序读取的应该是recombine_5final.msh这个最终文件。其他五个文件存在的意义是调试——当最终网格出现畸形单元时,你可以逐文件回溯,看问题出在拓扑修复阶段还是质量优化阶段。

2.2 msh 文件格式解析与代码实现

Gmsh 的 msh 格式有 2.2 和 4.1 两个常用版本,TestProject 里的文件是 2.2 版(旧版兼容性好,OCC 读取也方便)。解析逻辑并不复杂,核心是按关键字分段:

// Gmsh msh 2.2 格式解析核心代码(节选) #include <fstream> #include <sstream> #include <map> #include <vector> struct MshNode { int id; double x, y, z; }; struct MshElement { int type; // 1=三角形, 3=四边形, 4=四面体, 5=六面体 std::vector<int> nodeIds; }; bool parseMshFile(const std::string& path, std::vector<MshNode>& nodes, std::vector<MshElement>& elements) { std::ifstream in(path); if (!in.is_open()) return false; std::string line; bool inNodes = false, inElements = false; while (std::getline(in, line)) { if (line == "$Nodes") { inNodes = true; continue; } if (line == "$EndNodes") { inNodes = false; continue; } if (line == "$Elements") { inElements = true; continue; } if (line == "$EndElements") { inElements = false; continue; } if (inNodes) { std::istringstream iss(line); MshNode n; iss >> n.id >> n.x >> n.y >> n.z; nodes.push_back(n); } else if (inElements) { std::istringstream iss(line); int id, type, numTags; iss >> id >> type >> numTags; // 跳过 numTags 个标签 int tag; for (int i = 0; i < numTags; ++i) iss >> tag; MshElement e; e.type = type; int nodeId; while (iss >> nodeId) e.nodeIds.push_back(nodeId); elements.push_back(e); } } return true; }

注意这个解析器只处理了节点和单元两个段,而实际 msh 文件里还有$PhysicalNames$ElementData(比如质量标量场)。TestProject 之所以能精简掉这些段,是因为它的目标是可视化展示,不需要物理属性。如果你要拿这个程序去做有限元后处理,必须补上$ElementData段的解析,并且要把质量值映射到 OSG 的颜色数组上。

2.3 单元类型映射关系

Gmsh 的单元类型编号与可视化库的图元类型不是一一对应的,需要做映射。下面是 TestProject 需要的映射表:

Gmsh 类型编号Gmsh 含义OSG 对应图元顶点数
1三角形GL_TRIANGLES3
3四边形GL_QUADS(或三角化)4
4四面体GL_TRIANGLES(拆 4 个面)12
5六面体GL_QUADS(拆 6 个面)24

对于可视化,建议把体单元拆成表面三角面片,而不是直接用 GL_TETRAHEDRA——因为 OSG 的 primitive set 不支持体渲染,拆面后还能用背面剔除来提升帧率。常见的做法是在解析阶段就完成拆面,把节点坐标复制到对应的顶点数组中,虽然内存占用多了三倍,但省去了每帧做拓扑解码的 CPU 开销。

3. OCC 几何处理:从网格数据到实体模型的桥接

3.1 为什么要用 OCC 处理 Gmsh 网格

Gmsh 生成的是离散网格,而 OCC 的专长是连续几何(NURBS 曲面、布尔运算、放样)。如果你只是把 Gmsh 网格丢给 OSG 渲染,那完全不需要 OCC。但 TestProject 的集成方案里加入 OCC,是为了解决一个真实工程痛点:网格模型需要和 CAD 模型做对比验证,或者需要修改局部几何后再重新剖分。

常见做法是建立一条"网格 → OCC 拓扑 → 重新剖分"的闭环:把 Gmsh 的三角形面片用 OCC 的BRepBuilderAPI_MakeFace构造成曲面,然后利用 OCC 的 heal 工具修复缝隙和重叠,再导出为 STEP 或 IGES 供其他 CAD 软件使用。这也是 TestProject 里 OCC 部分的核心价值——它不是用来画图的,而是用来做几何修复和格式转换的。

3.2 用 OCC 构建 BRep 面并修复

// 将 Gmsh 三角形网格转换为 OCC 的 TopoDS_Face #include <BRepBuilderAPI_MakeFace.hxx> #include <BRepBuilderAPI_MakeWire.hxx> #include <BRepBuilderAPI_MakeEdge.hxx> #include <TopoDS_Shape.hxx> #include <gp_Pnt.hxx> #include <vector> // 输入:节点坐标和三角形索引 TopoDS_Shape buildShapeFromMesh( const std::vector<gp_Pnt>& points, const std::vector<std::vector<int>>& triangles) { // 用第一个三角形构造初始面 BRepBuilderAPI_MakeEdge edge1(points[triangles[0][0]], points[triangles[0][1]]); BRepBuilderAPI_MakeEdge edge2(points[triangles[0][1]], points[triangles[0][2]]); BRepBuilderAPI_MakeEdge edge3(points[triangles[0][2]], points[triangles[0][0]]); BRepBuilderAPI_MakeWire wire; wire.Add(edge1.Edge()); wire.Add(edge2.Edge()); wire.Add(edge3.Edge()); BRepBuilderAPI_MakeFace face(wire.Wire()); // 这里仅演示单个三角形的转换 // 实际项目需要先合并共享节点,再逐面构建,最后用 BRepAlgoAPI_Fuse 合成 return face.Shape(); }

这个示例只处理了一个三角形,真实场景下几百上千个三角形逐一构造面效率极低,而且会产生海量拓扑边。我在实际项目中一般不这样逐个构面,而是先把 Gmsh 网格写入 STL 文件,再用 OCC 的StlAPI_Reader直接导入:

#include <StlAPI_Reader.hxx> TopoDS_Shape readStlAsShape(const std::string& stlPath) { StlAPI_Reader reader; TopoDS_Shape shape; reader.Read(shape, stlPath.c_str()); return shape; }

StlAPI_Reader会把 STL 中的三角面片合并成 OCC 的 Compound 形状,速度比逐面构建快一个量级,而且自动处理共享顶点。这里有个参数细节:STL 是离散三角形描述,没有拓扑信息,导入后 OCC 生成的是一条条独立的边,做布尔运算前必须用ShapeFix_Shape进行修复。

3.3 交换格式:msh 与 STEP 之间的取舍

TestProject 里 OCC 和 Gmsh 之间没有直接的数据通道,它们是围绕 msh 文件解耦的。但如果你需要把 OCC 的实体模型交给 Gmsh 重新剖分,就必须经过中间格式。常见的交换路径是:

路径中间格式优点缺点
OCC → GmshSTEP (.step)保留 NURBS 精度Gmsh 对 STEP 曲面支持有限
OCC → GmshBREP完整拓扑Gmsh 原生需用 OpenCASCADE 内核编译
OCC → GmshSTL简单通用丢失曲面曲率,网格精度受限于三角形密度

我一般建议使用 STEP,因为 Gmsh 从 4.0 开始内置了 OpenCASCADE 内核,可以直接导入 STEP 并识别其中的面,自动生成符合曲率分布的网格。TestProject 没有演示这一步,因为它的重点是"网格 → 显示",而不是"实体 → 网格",你如果要做逆向流程,只需在 Gmsh 里Merge "model.step"即可。

4. OSG 可视化:把网格数据变成可交互场景

4.1 OSG 节点树与几何体构建

OSG 的核心思想是场景图(Scene Graph),所有可绘制对象都挂在一棵节点树下。TestProject 的渲染模块需要做三件事:把网格数据转换成osg::Geometry、把多个 Geometry 组织成osg::Geode、再把 Geode 挂到osg::Group下交给 Viewer。

// Gmsh 网格转 OSG Geometry #include <osg/Geometry> #include <osg/Geode> #include <osg/Group> #include <osgViewer/Viewer> #include <vector> osg::ref_ptr<osg::Geometry> buildGeometryFromMesh( const std::vector<float>& vertices, // 已拆分的三角面顶点,每 9 个 float 一个三角形 const std::vector<float>& normals) { // 法线数组,与顶点一一对应 osg::ref_ptr<osg::Geometry> geom = new osg::Geometry(); // 顶点数组 osg::ref_ptr<osg::Vec3Array> vArray = new osg::Vec3Array(); for (size_t i = 0; i < vertices.size(); i += 3) { vArray->push_back(osg::Vec3(vertices[i], vertices[i+1], vertices[i+2])); } geom->setVertexArray(vArray.get()); // 法线数组,用于光照 osg::ref_ptr<osg::Vec3Array> nArray = new osg::Vec3Array(); for (size_t i = 0; i < normals.size(); i += 3) { nArray->push_back(osg::Vec3(normals[i], normals[i+1], normals[i+2])); } geom->setNormalArray(nArray.get(), osg::Array::BIND_PER_VERTEX); // 图元类型:三角形列表 geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, vArray->size())); // 为每个顶点设置颜色(这里用单元质量伪彩,示例给灰色) osg::ref_ptr<osg::Vec4Array> color = new osg::Vec4Array(); for (size_t i = 0; i < vArray->size(); ++i) { color->push_back(osg::Vec4(0.7f, 0.7f, 0.7f, 1.0f)); } geom->setColorArray(color.get(), osg::Array::BIND_PER_VERTEX); return geom.get(); }

这里的参数绑定方式值得说明:BIND_PER_VERTEX意味着每个顶点都绑定一个法线和颜色,内存开销大但渲染最准确;如果模型只有平面面片,可以用BIND_PER_PRIMITIVE_SET为每个图元绑定一个法线,能省不少内存。TestProject 里如果网格很大(超过 100 万三角形),建议改用共享顶点的 DrawElements 方式,把顶点和索引分开存储,否则节点缓存会膨胀。

4.2 交互操作:旋转、缩放、平移的实现

OSG 默认的osgGA::TrackballManipulator已经提供了鼠标拖拽旋转、滚轮缩放、中键平移的全部功能,但 TestProject 要作为 CAD 测试程序,往往还需要自定义键盘快捷键来切换视图模式。

#include <osgGA/StateSetManipulator> #include <osgViewer/ViewerEventHandlers> osg::ref_ptr<osgViewer::Viewer> createViewer(osg::ref_ptr<osg::Group> root) { osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer(); viewer->setSceneData(root.get()); // 轨迹球操作器:支持旋转/缩放/平移 viewer->setCameraManipulator(new osgGA::TrackballManipulator()); // 键盘事件:按 w 切换线框模式,按 f 切换全屏 viewer->addEventHandler(new osgGA::StateSetManipulator( viewer->getCamera()->getOrCreateStateSet())); // 统计信息:按 s 显示帧率和绘制细节 viewer->addEventHandler(new osgViewer::StatsHandler()); return viewer; }

TrackballManipulator是默认操作器,CAD 场景里更多人会选osgGA::OrbitManipulator,区别在于轨道模式始终围绕目标点旋转,适合观察模型局部;轨迹球模式旋转中心会随着拖拽漂移,适合漫游。TestProject 没有强制指定,你可以在 viewer 的 setup 阶段用viewer->setCameraManipulator切换,参数上注意设置setHomePosition把相机初始位置放在网格包围球半径的 2 到 3 倍处,避免模型刚加载时被裁剪。

4.3 网格质量可视化:把 recombine_4quality.msh 的标量数据呈现出来

这是 TestProject 最有价值的一个扩展点。Gmsh 在质量优化阶段会为每个单元计算质量值(0 到 1,1 表示完美单元),这些值记录在 msh 文件的$ElementData段中。OSG 可以用它做伪彩渲染,让畸形单元一眼可见。

// 将单元质量映射为颜色 void applyQualityColors(osg::Geometry* geom, const std::vector<float>& quality) { osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(); for (float q : quality) { // 质量低显示红色,中等绿色,优秀蓝色 if (q < 0.5f) { colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f)); } else if (q < 0.8f) { colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f)); } else { colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f)); } } geom->setColorArray(colors.get(), osg::Array::BIND_PER_PRIMITIVE_SET); }

这个函数里我直接用了BIND_PER_PRIMITIVE_SET,因为质量值是单元级的,每个单元只有一个标量,不需要插值。如果你的网格质量参数是节点级的(Gmsh 也能输出节点质量),那就要改成BIND_PER_VERTEX并且在解析时把每个节点对应的质量值填进去。伪彩映射的阈值不是固定的,应该根据你的网格规模动态用分位数切分,否则绝大部分单元都挤在一个颜色段里,看不出问题。

5. 常用功能原理与实现

5.1 法线计算:从网格几何到光照效果

OSG 渲染时如果没有法线,模型就是一片黑的,因为默认光源的漫反射无法计算。TestProject 如果遇到网格没有法线的情况,需要在代码中显式计算。常见做法是按面计算法线再对顶点平均:

// 计算顶点法线,支持共享顶点 std::vector<float> computeVertexNormals( const std::vector<float>& vertices, const std::vector<unsigned int>& indices) { size_t numVertices = vertices.size() / 3; std::vector<float> normals(numVertices * 3, 0.0f); // 遍历每个三角形,累加面法线到三个顶点 for (size_t i = 0; i < indices.size(); i += 3) { unsigned int i0 = indices[i]; unsigned int i1 = indices[i+1]; unsigned int i2 = indices[i+2]; float ax = vertices[i1*3] - vertices[i0*3]; float ay = vertices[i1*3+1] - vertices[i0*3+1]; float az = vertices[i1*3+2] - vertices[i0*3+2]; float bx = vertices[i2*3] - vertices[i0*3]; float by = vertices[i2*3+1] - vertices[i0*3+1]; float bz = vertices[i2*3+2] - vertices[i0*3+2]; // 叉积得到面法线 float nx = ay * bz - az * by; float ny = az * bx - ax * bz; float nz = ax * by - ay * bx; normals[i0*3] += nx; normals[i0*3+1] += ny; normals[i0*3+2] += nz; normals[i1*3] += nx; normals[i1*3+1] += ny; normals[i1*3+2] += nz; normals[i2*3] += nx; normals[i2*3+1] += ny; normals[i2*3+2] += nz; } // 归一化 for (size_t i = 0; i < numVertices; ++i) { float len = sqrt(normals[i*3] * normals[i*3] + normals[i*3+1] * normals[i*3+1] + normals[i*3+2] * normals[i*3+2]); if (len > 1e-8) { normals[i*3] /= len; normals[i*3+1] /= len; normals[i*3+2] /= len; } } return normals; }

注意这段代码有个工程细节:如果索引数组里顶点是重复存储的(即非索引网格),那么累加法线会失败。所以在调用前要判断网格是否带索引。TestProject 解析 msh 后,建议统一转成索引网格,这样不仅法线计算正确,还能用osg::DrawElementsUInt减少显存占用。

5.2 网格域的可视化和相交检测

在某些场景下,需要点击模型上的某一点,然后判断这个点位于哪个网格域(Physical Group)里。Gmsh 的 msh 文件中每个单元可以带一个 physical tag,表示它的域编号。OSG 的相交检测接口是osgUtil::Intersector,可以返回命中点的坐标和 primitive 索引,再把这个索引映射回单元编号和域号。

常见做法是给每个域建一个独立的osg::Geode,这样相交检测的结果可以直接通过getNodePath()获取节点指针,然后从节点名中读出域号。TestProject 虽然没有实现拾取,但这个套路在网格质量检测工具里非常通用,代码结构不过是给每个 Geometry 加个setName("domain_3"),然后重写osgGA::GUIEventHandlerhandle方法调用computeIntersections

5.3 多文件加载与场景组织

一个 CAD 项目通常需要同时加载多个 msh 文件,比如一个零件包含流体域和固体域。TestProject 里的TestProject.cpp没有做多文件管理,但推荐的做法是维护一个std::map<std::string, osg::ref_ptr<osg::Node>>来管理场景节点,文件名作为 key。加载时用osgDB::readNodeFile读取已有的 osg 格式文件,而不是重新走一遍 msh 解析——这也是为什么我建议做一次性的 msh → osg 转换工具,把解析结果序列化,之后每次启动程序加载速度能快一个量级。

5.4 批量渲染和半透明

如果网格包含体单元(四面体、六面体),单一实体模式无法看到内部结构。常见做法是先渲染外层表面,再开启混合模式渲染内部单元:

// 设置半透明渲染状态 osg::ref_ptr<osg::StateSet> stateset = geom->getOrCreateStateSet(); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));

注意半透明渲染要先把外表面绘制完,再绘制内部体,否则深度测试会出错。OSG 的TRANSPARENT_BIN会自动把透明物体排在最后绘制,但如果内外物体都在同一个 bin 里且交叉,建议手动设置setRenderBinDetails(10, "DepthSortedBin")让 OSG 按深度排序。这在 TestProject 中并不是默认启用,需要你在场景根节点做一次遍历设置。

6. 进阶技巧:把 Gmsh 的 recombine 流程接到 OCC 场景树中

如果你要把 TestProject 从"显示网格"升级为"交互式网格优化工具",一个很实用的技巧是把 Gmsh 的 recombine 流程嵌入到 OCC 的场景树中,让用户在 OSG 视口中框选一片区域,程序自动对该区域的网格做重新剖分并实时刷新。实现上分三步:

第一步,用 Gmsh 的 C++ API 或命令行工具对局部区域重新剖分。Gmsh 支持在现有网格上定义新的尺寸函数和重组规则,核心命令是:

gmsh model.msh -recombine -order 2 -save -o model_optimized.msh

第二步,把新生成的 msh 解析为 OSG 节点,用osg::Switch节点同时挂载旧网格和新网格,通过开关控制显示。这样切换时不会出现场景空白:

osg::ref_ptr<osg::Switch> meshSwitch = new osg::Switch(); meshSwitch->addChild(oldGeom.get(), true); meshSwitch->addChild(newGeom.get(), false); // 切换显示 meshSwitch->setValue(0, false); meshSwitch->setValue(1, true);

第三步,也是最关键的一步:把质量指标写回 OCC 拓扑。Gmsh 计算出的单元质量可以通过gmsh::model::getQuality获取,但 OCC 本身不关心网格质量,它只关心几何形状。所以这里的正确做法是,用质量分布数据生成一份密度场,反馈给 OCC 的网格自适应模块,让 OCC 在下一次构建 BRep 时增加曲率大的区域的采样密度。常见做法是遍历质量低于 0.3 的单元,取它们的质心作为控制点,在 Gmsh 中设置局部尺寸函数:

// 使用 Gmsh C++ API 设置局部尺寸函数 gmsh::model::mesh::setSize(gmsh::model::getEntities(0), size); for (const auto& badCellCenter : badCellCenters) { gmsh::model::mesh::setSize(std::vector<std::pair<int,int>>{{0, 0}}, badCellCenter, 0.05); // 该点附近尺寸设为 0.05 } gmsh::model::mesh::generate(2);

这里setSize的第二个参数传入坐标点,Gmsh 会把该点作为尺寸函数的中心,形成向周围扩散的尺寸场。尺寸值越小,网格越密。这个参数需要根据模型尺度调整,TestProject 里的默认网格如果是 10x10x10 的立方体,0.05 的局部尺寸会产生非常密的网格,实际使用时建议从原始平均尺寸的 1/4 开始试。

最后说说验证方法。当你把 recombine 流程串进 OCC 场景后,应该同时显示单元 Jacobian 分布图和 Gmsh 自带的gmsh::model::mesh::getQuality返回的数值。如果优化后最小 Jacobian 不升反降,优先检查是否把物理域标识搞丢了——recombine 后的 msh 文件如果没有$PhysicalNames段,OCC 的 ShapeFix 会认为所有面都是独立的,导致后续布尔运算产生非流形边。此时回退到recombine_3topo.msh那个阶段重新处理,不要直接改质量优化参数。掌握了这个环节,TestProject 就从一个静态查看器变成了能驱动 Gmsh 做网格自适应的测试平台。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 12:59:07

软考网管初级计算机硬件基础:高频考点与备考攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 12:59:00

STM32F3xx DDS信号发生器工程解析与实战

简介&#xff1a;本资源为2023年全国大学生电子设计竞赛实战代码合集&#xff0c;面向备赛本科生及嵌入式开发初学者&#xff0c;聚焦STM32平台下的高频考点实现与工程化落地。包内含177个文件&#xff0c;以49个头文件&#xff08;h&#xff09;和25个C源码&#xff08;c&…

作者头像 李华
网站建设 2026/9/13 12:56:40

K-means聚类算法原理与Python实现详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 12:53:05

单机无穷大系统仿真:从Simulink建模到暂态稳定分析

简介&#xff1a;单机无穷大系统是电力系统暂态稳定性分析的经典简化模型&#xff0c;这份MATLAB脚本仿真代码面向电力系统专业学生、研究人员及工程技术人员&#xff0c;用于研究一台发电机经无穷大母线接入电网时的功率振荡与稳定恢复特性。压缩包内仅含1个m文件&#xff0c;…

作者头像 李华