发布时间:2026/8/26 16:55:04
bambustudio 选择底面 修复 目录设置和选择设备自动修复结束bambustudio 选择底面1. 鼠标点击面 → 获取法向量2. m_normal 的填充3. 实际旋转执行 ← 核心调用链总结检测平面设置和选择设备m_device_manager-set_selected_machine(obj-get_dev_id()); MachineObject* obj_ dev-get_selected_machine();自动修复结束void Plater::priv::on_repair_model(wxCommandEvent event) { wxGetApp().obj_list()-fix_through_netfabb(); }bambustudio 选择底面选择底面Lay on face功能的完整调用链1. 鼠标点击面 → 获取法向量GLGizmosManager::on_mouseGLGizmosManager.cpp:983-997用户点击某个面grabber时检测到m_current Flatten调用m_parent.do_flatten(get_flattening_normal(), L(Tool-Lay on Face));其中get_flattening_normal()GLGizmosManager.cpp:548转发到GLGizmoFlatten::get_flattening_normal()GLGizmoFlatten.cpp:571返回并清空成员变量m_normal。2.m_normal的填充GLGizmoFlatten::on_start_draggingGLGizmoFlatten.cpp:111-123cppif (m_hover_id ! -1) { if (m_faltten_type FlattenType::Default) m_normal m_planes[m_hover_id].normal; // 从预计算的平面取法向量 else m_normal m_hit_object_normal.castdouble(); // Triangle 模式取射线命中面法向量 }FlattenType::Default点击预计算好的平面图标大平面m_planes由update_planes()GLGizmoFlatten.cpp:296生成FlattenType::Triangle点击任意三角形面法向量来自update_raycast_cacheGLGizmoFlatten.cpp:125中的射线检测3. 实际旋转执行 ←核心GLCanvas3D::do_flattenGLCanvas3D.cpp:6420-6427cppvoid GLCanvas3D::do_flatten(const Vec3d normal, const std::string snapshot_type) { if (!snapshot_type.empty()) wxGetApp().plater()-take_snapshot(snapshot_type); m_selection.flattening_rotate(normal); // ← 旋转 do_rotate(); // 触发重绘/同步 }Selection::flattening_rotateSelection.cpp:1491-1526是真正执行旋转的函数cpp// 将选中面的法向量旋转到朝下-Z方向 const Transform3d rotation_matrix Transform3d(Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ())); v.set_instance_transformation( old_inst_trafo.get_offset_matrix() * rotation_matrix * old_inst_trafo.get_matrix_no_offset());核心逻辑用Eigen::Quaterniond::setFromTwoVectors(tnormal, -Vec3d::UnitZ())计算一个旋转把选中面的法向量tnormal对齐到世界坐标的-Z朝下然后写回 instance 变换矩阵。调用链总结鼠标点击面 → GLGizmosManager::on_mouse (GLGizmosManager.cpp:994) → get_flattening_normal() (GLGizmoFlatten.cpp:571) └ m_normal 在 on_start_dragging() 中填充 (GLGizmoFlatten.cpp:111) → GLCanvas3D::do_flatten() (GLCanvas3D.cpp:6420) → Selection::flattening_rotate() (Selection.cpp:1491) ← 实际旋转如果你想程序化触发选择底面需要设置好m_normal后调用do_flatten。m_normal是 private 成员正常路径只能通过点击触发on_start_dragging填充——程序化调用需手动构造法向量并直接调do_flatten。检测平面void GLGizmoFlatten::update_planes() { const ModelObject* mo m_c-selection_info()-model_object(); TriangleMesh ch; for (const ModelVolume* vol : mo-volumes) { if (vol-type() ! ModelVolumeType::MODEL_PART) continue; TriangleMesh vol_ch vol-get_convex_hull(); vol_ch.transform(vol-get_matrix()); ch.merge(vol_ch); } ch ch.convex_hull_3d(); m_planes.clear(); const Transform3d inst_matrix mo-instances.front()-get_matrix(true); // Following constants are used for discarding too small polygons. const float experted_minimal_area 5.0f; const float minimal_area 1.0f; // in square mm (world coordinates) const float minimal_side 1.f; // mm const float minimal_angle 1.f; // degree, initial value was 10, but cause bugs // Now well go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. const int num_of_facets ch.facets_count(); const std::vectorVec3f face_normals its_face_normals(ch.its); const std::vectorVec3i face_neighbors its_face_neighbors(ch.its); std::vectorint facet_queue(num_of_facets, 0); std::vectorbool facet_visited(num_of_facets, false); int facet_queue_cnt 0; const stl_normal* normal_ptr nullptr; int facet_idx 0; while (1) { // Find next unvisited triangle: for (; facet_idx num_of_facets; facet_idx) if (!facet_visited[facet_idx]) { facet_queue[facet_queue_cnt ] facet_idx; facet_visited[facet_idx] true; normal_ptr face_normals[facet_idx]; m_planes.emplace_back(); break; } if (facet_idx num_of_facets) break; // Everything was visited already while (facet_queue_cnt 0) { int facet_idx facet_queue[-- facet_queue_cnt]; const stl_normal this_normal face_normals[facet_idx]; if (std::abs(this_normal(0) - (*normal_ptr)(0)) 0.001 std::abs(this_normal(1) - (*normal_ptr)(1)) 0.001 std::abs(this_normal(2) - (*normal_ptr)(2)) 0.001) { const Vec3i face ch.its.indices[facet_idx]; for (int j0; j3; j) m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].castdouble()); facet_visited[facet_idx] true; for (int j 0; j 3; j) if (int neighbor_idx face_neighbors[facet_idx][j]; neighbor_idx 0 ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ] neighbor_idx; } } m_planes.back().normal normal_ptr-castdouble(); Pointf3s verts m_planes.back().vertices; // Now well transform all the points into world coordinates, so that the areas, angles and distances // make real sense. verts transform(verts, inst_matrix); // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): if (verts.size() 3 ((verts[0] - verts[1]).norm() minimal_side || (verts[0] - verts[2]).norm() minimal_side || (verts[1] - verts[2]).norm() minimal_side)) m_planes.pop_back(); } // Lets prepare transformation of the normal vector from mesh to instance coordinates. Geometry::Transformation t(inst_matrix); Vec3d scaling t.get_scaling_factor(); t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); // Now well go through all the polygons, transform the points into xy plane to process them: for (int polygon_id0; polygon_id m_planes.size(); polygon_id) { Pointf3s polygon m_planes[polygon_id].vertices; const Vec3d normal m_planes[polygon_id].normal; // transform the normal according to the instance matrix: Vec3d normal_transformed t.get_matrix() * normal; // We are going to rotate about z and y to flatten the plane Eigen::Quaterniond q; Transform3d m Transform3d::Identity(); m.matrix().block(0, 0, 3, 3) q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); polygon transform(polygon, m); // Now to remove the inner points. Well misuse Geometry::convex_hull for that, but since // it works in fixed point representation, we will rescale the polygon to avoid overflows. // And yes, it is a nasty thing to do. Whoever has time is free to refactor. Vec3d bb_size BoundingBoxf3(polygon).size(); float sf std::min(1./bb_size(0), 1./bb_size(1)); Transform3d tr Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); polygon transform(polygon, tr); polygon Slic3r::Geometry::convex_hull(polygon); polygon transform(polygon, tr.inverse()); // Calculate area of the polygons and discard ones that are too small float area m_planes[polygon_id].area; area 0.f; for (unsigned int i 0; i polygon.size(); i) // Shoelace formula area polygon[i](0)*polygon[i 1 polygon.size() ? i 1 : 0](1) - polygon[i 1 polygon.size() ? i 1 : 0](0)*polygon[i](1); area 0.5f * std::abs(area); bool discard false; if (area minimal_area) discard true; else { // We also check the inner angles and discard polygons with angles smaller than the following threshold const double angle_threshold ::cos(minimal_angle * (double)PI / 180.0); for (unsigned int i 0; i polygon.size(); i) { const Vec3d prec polygon[(i 0) ? polygon.size() - 1 : i - 1]; const Vec3d curr polygon[i]; const Vec3d next polygon[(i polygon.size() - 1) ? 0 : i 1]; if ((prec - curr).normalized().dot((next - curr).normalized()) angle_threshold) { discard true; break; } } } if (discard) { m_planes[polygon_id] std::move(m_planes.back()); m_planes.pop_back(); polygon_id--; continue; } // We will shrink the polygon a little bit so it does not touch the object edges: Vec3d centroid std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); centroid / (double)polygon.size(); for (auto vertex : polygon) vertex 0.9f*vertex 0.1f*centroid; // Polygon is now simple and convex, well round the corners to make them look nicer. // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex // towards their average (controlled by aggressivity). This is repeated k times. // In next iterations, the neighbours are not always taken at the middle (to increase the // rounding effect at the corners, where we need it most). const unsigned int k 10; // number of iterations const float aggressivity 0.2f; // agressivity const unsigned int N polygon.size(); std::vectorstd::pairunsigned int, unsigned int neighbours; if (k ! 0) { Pointf3s points_out(2*k*N); // vector long enough to store the future vertices for (unsigned int j0; jN; j) { points_out[j*2*k] polygon[j]; neighbours.push_back(std::make_pair((int)(j*2*k-k) 0 ? (N-1)*2*kk : j*2*k-k, j*2*kk)); } for (unsigned int i0; ik; i) { // Calculate middle of each edge so that neighbours points to something useful: for (unsigned int j0; jN; j) if (i0) points_out[j*2*kk] 0.5f * (points_out[j*2*k] points_out[jN-1 ? 0 : (j1)*2*k]); else { float r 0.20.3/(k-1)*i; // the neighbours are not always taken in the middle points_out[neighbours[j].first] r*points_out[j*2*k] (1-r) * points_out[neighbours[j].first-1]; points_out[neighbours[j].second] r*points_out[j*2*k] (1-r) * points_out[neighbours[j].second1]; } // Now we have a triangle and valid neighbours, we can do an iteration: for (unsigned int j0; jN; j) points_out[2*k*j] (1-aggressivity) * points_out[2*k*j] aggressivity*0.5f*(points_out[neighbours[j].first] points_out[neighbours[j].second]); for (auto n : neighbours) { n.first; --n.second; } } polygon points_out; // replace the coarse polygon with the smooth one that we just created } // Raise a bit above the object surface to avoid flickering: for (auto b : polygon) b(2) 0.1f; // Transform back to 3D (and also back to mesh coordinates) polygon transform(polygon, inst_matrix.inverse() * m.inverse()); } if (m_planes.size() 0) { m_show_warning true; return; } // Well sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData a, const PlaneData b) { return a.area b.area; }); auto delte_index_to_end [](int index, std::vectorPlaneData planes) { for (size_t i planes.size() - 1; i index; i--) { planes.pop_back(); } }; const int plane_count 30; for (size_t i 0; i m_planes.size(); i) { if (m_planes[i].area experted_minimal_area) { if (i 1 plane_count) { delte_index_to_end(plane_count, m_planes); break; } else {//plane_count for (size_t j i 1; j m_planes.size(); j) { if (j 1 plane_count) { delte_index_to_end(plane_count, m_planes); break; } } break; } } } m_planes.resize(std::min((int)m_planes.size(), 254)); // Planes are finished - lets save what we calculated it from: m_volumes_matrices.clear(); m_volumes_types.clear(); for (const ModelVolume* vol : mo-volumes) { m_volumes_matrices.push_back(vol-get_matrix()); m_volumes_types.push_back(vol-type()); } m_first_instance_scale mo-instances.front()-get_scaling_factor(); m_first_instance_mirror mo-instances.front()-get_mirror(); m_old_model_object mo; m_old_instance_id m_c-selection_info()-get_active_instance(); // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto plane : m_planes) { plane.vbo.reserve(plane.vertices.size()); for (const auto vert : plane.vertices) plane.vbo.push_geometry(vert, plane.normal); for (size_t i1; iplane.vertices.size()-1; i) plane.vbo.push_triangle(0, i, i1); // triangle fan plane.vbo.finalize_geometry(true); // FIXME: vertices should really be local, they need not // persist now when we use VBOs plane.vertices.clear(); plane.vertices.shrink_to_fit(); } m_show_warning false; m_planes_valid true; }

相关新闻

2026/8/26 16:55:04

工业清洗剂安全性深度评测:从成分到实测的全方位验证

在工业生产和实验室环境中,化学试剂的选型往往直接关系到一线操作人员的安全底线。很多技术团队在引入新溶剂或清洗剂时,容易陷入“只看参数表”的误区,忽略了实际工况中复杂的物理化学反应。一旦选错材料,轻则导致设备表面腐蚀、…

2026/8/26 17:40:14

TCP 粘包问题:产生原因分析与嵌入式场景解决方法

一、什么是 TCP 粘包问题在嵌入式 TCP 透传、串口转 WiFi 等项目开发中,经常会遇到这类异常:设备端连续发送温度采集、湿度采集两条独立指令,上位机却只收到一条合并的数据;或是消息解析到一半突然错位,导致后续所有业…

2026/8/26 17:35:14

手机版deepseek里复制代码怎么用?AI导出鸭一键无损导出格式不崩

手机版DeepSeek生成代码后直接复制粘贴到本地编辑器,缩进全乱、换行符丢失、特殊字符变问号——这是移动端开发者和学习者的高频痛点。根源在于手机浏览器剪贴板对富文本和纯文本的转换逻辑不统一,复制时丢失了代码的缩进层级、空行和语法高亮元信息。AI…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 11:48:27

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 16:56:43

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 0:04:32

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 1:19:35

JSON总结

JSON概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,主要用于跟服务器进行交换数据。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScr…

2026/8/26 1:19:35

保存连接sse 是什么原理,为什么不会一直请求

“保持连接”用的是 SSE(Server-Sent Events),本质是一个没有马上结束的 HTTP 请求。 过程是: 拷贝机发送一次请求: GET /api/code-sync/events服务器返回: Content-Type: text/event-stream但不关闭响应&…

2026/8/24 13:42:17

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/24 18:13:48

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/25 1:08:14

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…