SolidCraft 接触集(Contact Sets)完全指南:让装配体关节运动在碰撞处精确停下

原创2026-10-13 18:57:521,899 阅读
文章标签:图形学3D渲染桌面应用MCP 服务

SolidCraft 接触集(Contact Sets)完全指南:让装配体关节运动在碰撞处精确停下

本文是 SolidCraft 装配体接触集(Contact Sets)机制的深度实战指南。接触集用于约束 Drive Joints(驱动关节)、运动研究(Motion Study)回放以及运动导出(motion.export)的轨迹:只要启用了某套接触集,其成员在请求的关节运动轨迹上发生碰撞,运动就会被截停——包括滑块撞上挡块、回转副越过挡块的情形。读完本文,你将掌握接触集的创建、编辑、启用与检查命令,理解 stopped_by_contact 返回结构,并了解其基于缓存网格采样的轨迹检测原理、预算限制、恢复行为与已知局限。

接触集是什么:核心机制与适用场景

在 SolidCraft 中,接触集是一组**装配体出现项(occurrence)**的集合,数据模型定义在 crates/doc/src/motion.rs:

/// Occurrences whose bodies may touch but not pass through each other while joints move.
pub struct ContactSet {
    pub id: u64,
    pub name: String,
    pub occurrences: Vec<u64>,
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub suppressed: bool,
}

每个接触集由 id、name、成员出现项列表 occurrences 和 suppressed(抑制开关)构成。其工作机制可概括为:

  • 属于同一套已启用接触集中不同成员(member)的实体,会在被请求的关节运动过程中被逐点检查;
  • 如果起点与终点之间存在碰撞,运动即被截停——所以滑块滑向挡块会停在接触处,回转副扫过挡块也会停在接触处;
  • 运动的截停由 stopped_by_contact 字段回报,指明碰撞的出现项对以及运动被推进到的比例。

接触集约束的三类运动场景(docs/contact-sets.md):

  1. joint.drive 驱动的关节;
  2. motion.play 运动研究回放;
  3. motion.export 运动导出(含稀疏导出)。

快速上手:创建接触集并驱动关节

接触集的最小工作流只需两条命令(docs/contact-sets.md):

{"command":"contact.create","params":{"occurrences":["Base:1","Carriage:1"]}}
{"command":"joint.drive","params":{"joint":"Rail","value":30}}

第一条命令把 Base:1 与 Carriage:1 两个出现项划入同一个接触集;第二条命令驱动名为 Rail 的关节到值 30。若滑轨行程中 Carriage 会撞上 Base,joint.drive 会停在碰撞前的最后安全位置,并在返回中携带 stopped_by_contact 对象(见下文「停止机制与返回结果」)。

关于 contact.create 的输入约束,从 crates/engine/src/cmd/motion.rs 的实现可以确认:

  • occurrences 必须是一个数组,至少包含 2 个出现项(少于两个会报错 a contact set needs two or more occurrences);
  • 支持传出现项 ID 或名称,会去重;
  • 根(root)不是出现项,不能加入(the root is not an occurrence);
  • name 可选,缺省时自动命名为 Contact Set{n};
  • 创建后会自动把 contact_enabled 置为 true(motion.rs)。

接触集全套命令

接触集相关命令在 docs/features.md 中有完整登记,共 7 个 contact.* 命令:

命令 用途 主要参数
contact.enable_sets 启用接触集 enabled?: bool(缺省 true)
contact.enable_all 启用全部接触 所有出现项组成一套全集
contact.disable_all 关闭接触 无
contact.create 新建接触集 occurrences(≥2 个);name?
contact.edit 编辑接触集 set: id|name;occurrences?;name?;suppressed?: bool;delete?: bool
contact.list 列出接触集 返回 sets、enabled、all
contact.check 检查当前接触 返回当前互相穿透的出现项对

各命令参数定义同样可以在 crates/engine/src/cmd/motion.rs 的 COMMANDS 表中找到。其中:

  • contact.edit 可以修改成员、改名、抑制(suppressed: true 使该集不参与检查)或删除(delete: true),删除与编辑逻辑见 motion.rs;
  • contact.check 返回 {"collisions": <a href="https://link.gitcode.com/i/ce0bfa672f9973b88f1936777884c3a4" target="_blank">{"a": ..., "b": ..., "depth_mm": ...}, ...]},其中 depth_mm 是一方深入另一方的最大穿透深度,实现见 [motion.rs;
  • contact.enable_all 会把所有出现项放入一个全量集合,任何两个出现项之间都执行接触检查(motion.rs)。

停止机制与 stopped_by_contact 返回结构

当运动因接触被截停,命令返回中的 stopped_by_contact 字段会是一个对象,标识碰撞出现项对与到达比例(docs/contact-sets.md):

{
  "a": "Carriage:1",
  "b": "Base:1",
  "fraction": 0.73
}
  • a / b:发生首次碰撞的两个出现项名称;
  • fraction:被请求运动被推进到的比例(0.0~1.0),0 表示一动就撞。

stopped_by_contact 由 drive_with_contact 计算产生(crates/engine/src/cmd/motion.rs):驱动时首先记录当前关节值为起点,然后在 0, 1] 区间上按采样网格逐步试探,一旦某采样点出现碰撞,就锁定该采样区间,并通过 14 轮二分细化(for _ in 0..14)把停止位置精确定位到"恰好差一点点不穿透"——也就是说,最终停点保持在细分网格接触容差之内,实体刚好在接触面附近停下,而不是穿入对方。对应测试 [crates/engine/src/cmd/motion_tests.rs 断言了这一点:停点与理论接触面误差小于 2e-3("a touch, not a pass")。

joint.drive 的完整返回结构(crates/engine/src/cmd/joints.rs)包含关节 ID、最终 values 以及 stopped_by_contact;未发生碰撞时该字段为 null。注意:刚性关节(rigid)不能被驱动,驱动命令会直接报错;未显式给出的自由度值会保持原值,且目标值会先经过关节限位(joints::clamp)约束。

轨迹采样算法:检测密度如何决定

接触检查不是一次性检查起点与终点两个姿态,而是沿轨迹做有界采样(bounded samples),核心实现在 crates/engine/src/cmd/contact_sweep.rs:

  1. 网格缓存:接触集成员实体的细分网格会被缓存(按 body 名称去重),供整条轨迹重复使用;
  2. 采样步长:step = min(body 最小厚度) * 0.25。厚度的计算同时考虑包围盒的最小边长与沿各区分面法向的整体系宽度投影——最多收集 32 个有区分的面法向方向,把整个 body 沿这些方向的投影宽度取最小值作为厚度;
  3. 采样数量:在 contact_sweep.rs 中,samples() 综合了:
    • 有效关节行程:所有非抑制关节上各自由度位移绝对值之和(linear);
    • 角运动:角度型自由度的位移量(angular),并通过起点/终点姿态下关节原点到全局包围盒最远点的距离(radius)折算扫过的弧长;
    • 步长:required = ceil(max((linear + angular * (radius + linear)) / step, angular / 1_rad))。

这套设计的效果(有对应测试佐证):

  • 倾斜薄板不会因为轴对齐采样而"漏穿":thin_tilted_plates_cannot_cross_between_axis_bound_samples(contact_sweep_tests.rs)用两块 55° 倾斜的 0.05 mm 薄板验证,驱动 -4 时正确停在约 -2.0 处;
  • 曲面实体保持实用步长:ordinary_curved_bodies_keep_a_practical_sampling_step(contact_sweep_tests.rs)断言球体/圆角体的采样步长 geometry.step > 1.0,说明曲面细分不会把步长压到不实用的微小值。

碰撞判定的底层是网格重叠检测(motion.rs):对两副封闭网格,先做 AABB 快速剔除,再用一方网格的顶点、边中点、三角形中心等采样点对另一方做**射线奇偶性(ray parity)**内部判定,取最深穿透距离与容差(1e-4 × size)比较。

预算限制与错误恢复

接触扫描对装配体规模、几何复杂度与工作量设有硬性预算(contact_sweep.rs):

常量 值 含义
MAX_SAMPLES 4096 单次轨迹最大采样数
MAX_PLACEMENTS 4096 装配出现项遍历上限(路径深度 ≤ 64)
MAX_BODIES 256 接触参与实体数量上限
MAX_TRIANGLES 50 000 单个实体网格三角形上限
MAX_WORK 50 000 000 累计碰撞计算工作量上限

当运动或装配体超出采样、几何或碰撞工作量预算时,命令返回错误并恢复文档——测试 over_budget_sweep_errors_and_restores_document(contact_sweep_tests.rs)断言:超预算报错后文档内容与关节值均恢复原状。对应错误信息如:

  • motion exceeds contact sweep sampling budget; drive the joint in smaller increments
  • motion exceeds contact sweep transform budget; simplify the contact set or use smaller increments
  • motion exceeds contact sweep collision budget; simplify the contact set or use smaller increments
  • body is too thin for a bounded contact sweep

因此文档给出的实用建议是:改用更小的驱动增量,或缩小接触集范围。另外,接触集内的 Mesh 实体(网格体)目前不支持扫描驱动,会返回 contact sweep does not support mesh bodies 错误,需要先禁用其接触集再移动。

已知局限:不是精确连续碰撞检测

接触扫描是有界采样而非精确的连续碰撞检测(continuous collision detection),这一点在 docs/contact-sets.md 中明确说明,并得到实现注释的印证(contact_sweep.rs)。因此存在以下固有局限:

  • 大实体中的局部薄壁:薄度按"整体系宽度"计算,某个宽大体内部局部很薄的部分不会被当作薄材料处理,可能漏检;
  • 未采样的面方向:轨迹采样点与检测方向是离散的,被采样间隙错过的方向可能漏掉碰撞;
  • 极小的几何细节:远小于采样步长的特征可能不被发现。

在 ROADMAP.md 中也明确列出这些限制("local thin walls and small details remain limitations")。设计上,这类任务应缩小驱动增量或细分接触集,必要时改用碰撞检查(contact.check)人工确认当前姿态。

初始重叠与恢复行为

如果装配体在起点姿态就已经互相穿透,接触扫描会保留"恢复(recovery)"行为,允许其移出非法占位,而不是卡死在原地。实现上,drive_with_contact 在起点先调用 cache.first_hit(...),若起点就有碰撞则不再施加截停约束,直接允许运动(motion.rs)。测试 initially_overlapping_assembly_can_move_out_for_recovery(contact_sweep_tests.rs)验证了:在 contact.disable_all 下驱动到重叠姿态,重新启用接触后,joint.drive 移出重叠位置时 stopped_by_contact 为 null,移动完全自由。

如需完全自由移动(不受任何接触约束),使用 contact.disable_all 即可,之后驱动将跳过接触扫描预算,可以一次移动任意大距离(测试 slider_cannot_tunnel_to_a_collision_free_endpoint 中用 -100000 验证了禁用后的行为,contact_sweep_tests.rs)。

运动研究:回放与导出同样受接触约束

运动研究(Motion Study)把关节值沿时间轴排布成关键帧(crates/doc/src/motion.rs),其回放与导出复用同一套接触扫描:

  • motion.play:把研究在某 step 处的关节值作为目标执行 drive_with_contact,若中途撞上接触集成员则停在接触处并回报 stopped_by_contact(motion.rs);
  • motion.export:在**文档的临时副本(scratch)**上逐样本驱动并记录各出现项位姿,源文档保持不变——测试 motion_play_and_sparse_export_share_swept_contact_checks 用 Arc::ptr_eq 验证导出前后文档指针相同(contact_sweep_tests.rs)。

motion.export 关键参数(motion.rs):

  • study:研究 ID 或名称;
  • samples:2~10000,缺省为 steps + 1(上限 10000);导出支持稀疏采样;
  • path?:输出 .json 或 .csv 文件,CSV 表头为 step,occurrence,x,y,z,xx,xy,xz,yx,yy,yz,zx,zy,zz(每样本每出现项一行),JSON 则输出每样本的 positions 与 stopped_by_contact。

一个重要语义(docs/contact-sets.md):接触检查会在请求位姿之间线性插值关节值,而不会重建被请求采样步省略掉的中间运动关键帧。也就是说,稀疏导出不会凭空"脑补"关键帧之间的运动学细节,检查的是插值轨迹上的碰撞。测试 motion_study_plays_and_exports 验证了稀疏导出(samples: 2)的每个样本都做了接触检查且文档保持不变(motion_tests.rs)。

运动链接与限位参与轨迹检查

接触扫描并非只检查被驱动的那一个关节,而是检查整条轨迹上的所有关节:

  • 运动链接(Motion Link):joint.motion_link 建立的联动关系会参与轨迹检查——驱动输入关节时,被链接关节按 ratio 同步移动,接触检查按"有效行程"计算。测试 linked_slider_uses_effective_travel_for_contact(contact_sweep_tests.rs)用 ratio=10 的联动滑块验证:驱动 -3 时因联动放大成 -30 的有效行程,停在 0 附近;
  • 关节限位(limits):目标值在采样前经过 joints::clamp,限位内的轨迹才被检查(motion.rs);
  • 被抑制的关节(suppressed)不参与有效行程计算(contact_sweep.rs)。

跨组件移动实体:body offset 保持接触位姿

把一个实体移动到另一个组件时,其世界位姿通过**body offset(实体偏移)**得到保留,接触检查与爆炸视图中心都使用该偏移。placed()(motion.rs)和接触扫描的 frame()(contact_sweep.rs)都在出现项世界变换上叠加 model_body_offset。测试 contact_uses_preserved_body_placements_after_component_reassignment(motion_tests.rs)验证了:实体被移入新组件并平移后,contact.check 仍按原世界位置正确报告碰撞。

界面入口与自动化

在桌面 UI 中,接触集命令位于 SOLID › ASSEMBLE 菜单下(docs/features.md),弹窗实现在 crates/ui-egui/src/dialogs_motion.rs,其中 "Contact Set" 对话框通过 SelInput::new("Components", BODIES, true) 提供多选组件输入,最终构造成 contact.create 命令下发。该命令同时登记在 workspace.rs 的工作区命令集合中,说明它也是 UI 会话可执行的一等命令。

无论是桌面端手工操作,还是通过 MCP/命令行脚本自动化,接触集都遵循同一套命令语义,便于把"带碰撞约束的装配运动仿真"集成进测试或批量任务。

结语

接触集是 SolidCraft 装配模块中把"关节运动"与"几何干涉"关联起来的关键机制:它约束 joint.drive、运动研究回放与运动导出,通过缓存网格采样与二分细化在碰撞处精确截停运动,并以 stopped_by_contact 返回碰撞对与到达比例。理解其采样算法、预算限制与恢复行为,能帮助你在实践中正确设计接触集——必要时使用更小驱动增量、更精简的集合,并善用 contact.check 验证当前姿态,从而获得可靠且高效的装配运动仿真结果。

登录后查看全文
solidcraft