新增曲柄滑块批量仿真接口
This commit is contained in:
@@ -406,6 +406,44 @@ func
|
|||||||
- 命令层负责把参数取出来、调机制对象、再把结果组装成 JSON
|
- 命令层负责把参数取出来、调机制对象、再把结果组装成 JSON
|
||||||
- 真正的机构计算在 `src/FourBarMechanism/CrankSliderMechanism.cpp`
|
- 真正的机构计算在 `src/FourBarMechanism/CrankSliderMechanism.cpp`
|
||||||
|
|
||||||
|
### 6.2 `Cmd_FourBar_CrankSlider_Simulate`
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
- 输入曲柄滑块初始参数、起始角度、结束角度、总时长和步长时间
|
||||||
|
- 一次性生成整段仿真帧数据,适合前端播放、导出轨迹和离线验证
|
||||||
|
|
||||||
|
调用履历:
|
||||||
|
|
||||||
|
```text
|
||||||
|
func
|
||||||
|
-> dispatchCommand
|
||||||
|
-> handleFourBarCommand
|
||||||
|
-> createCrankSliderMechanism
|
||||||
|
-> CrankSliderMechanism::setL_AB / setL_BS / setS_OFS
|
||||||
|
-> CrankSliderMechanism::validateParameters
|
||||||
|
-> for each frame:
|
||||||
|
-> CrankSliderMechanism::calculate(currentAngleDeg)
|
||||||
|
-> CrankSliderMechanism::getTrajectoryPoints
|
||||||
|
-> CrankSliderMechanism::getSliderTrajectory
|
||||||
|
```
|
||||||
|
|
||||||
|
主要入参:
|
||||||
|
|
||||||
|
- `L_AB`:曲柄长度
|
||||||
|
- `L_BS`:连杆长度
|
||||||
|
- `S_OFS`:滑块偏移
|
||||||
|
- `startAngleDeg`:起始曲柄角度
|
||||||
|
- `endAngleDeg`:结束曲柄角度
|
||||||
|
- `duration`:仿真总时长,单位秒
|
||||||
|
- `stepTime`:仿真步长时间,单位秒
|
||||||
|
|
||||||
|
主要出参:
|
||||||
|
|
||||||
|
- `frames[]`:逐帧仿真数据,每帧包含 `frame`、`time`、`angleDeg`、`points`、`poses`、`angles`
|
||||||
|
- `trajectory`:B 点整段轨迹
|
||||||
|
- `slider_trajectory`:S 点整段滑块轨迹
|
||||||
|
|
||||||
## 7. 四足机器人接口履历
|
## 7. 四足机器人接口履历
|
||||||
|
|
||||||
命令文件:
|
命令文件:
|
||||||
@@ -596,4 +634,3 @@ smart_test_match
|
|||||||
1. `src/api/KinematicsWebAPI.SpcCommands.cpp`
|
1. `src/api/KinematicsWebAPI.SpcCommands.cpp`
|
||||||
2. `src/spc_core.cpp`
|
2. `src/spc_core.cpp`
|
||||||
3. `include/spc_core.h`
|
3. `include/spc_core.h`
|
||||||
|
|
||||||
|
|||||||
@@ -579,20 +579,24 @@
|
|||||||
<input id="angleInput" type="number" step="1" />
|
<input id="angleInput" type="number" step="1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="stepInput">扫描步长</label>
|
<label for="stepInput">stepTime 步长时间</label>
|
||||||
<input id="stepInput" type="number" min="1" max="90" step="1" />
|
<input id="stepInput" type="number" min="0.01" max="1" step="0.01" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="scanEndInput">扫描终止角</label>
|
<label for="scanEndInput">扫描终止角</label>
|
||||||
<input id="scanEndInput" type="number" min="1" max="720" step="1" />
|
<input id="scanEndInput" type="number" min="1" max="720" step="1" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="durationInput">duration 总时长</label>
|
||||||
|
<input id="durationInput" type="number" min="0.02" max="60" step="0.01" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="secondary" id="validButton" type="button">有效样例</button>
|
<button class="secondary" id="validButton" type="button">有效样例</button>
|
||||||
<button class="secondary" id="invalidButton" type="button">异常样例</button>
|
<button class="secondary" id="invalidButton" type="button">异常样例</button>
|
||||||
<button class="primary" id="runButton" type="button">调用接口</button>
|
<button class="primary" id="runButton" type="button">调用接口</button>
|
||||||
<button class="primary" id="scanButton" type="button">角度扫描</button>
|
<button class="primary" id="scanButton" type="button">仿真扫描</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="message" id="inputMessage" aria-live="polite"></div>
|
<div class="message" id="inputMessage" aria-live="polite"></div>
|
||||||
|
|
||||||
@@ -756,6 +760,7 @@
|
|||||||
const angleInput = document.getElementById("angleInput");
|
const angleInput = document.getElementById("angleInput");
|
||||||
const stepInput = document.getElementById("stepInput");
|
const stepInput = document.getElementById("stepInput");
|
||||||
const scanEndInput = document.getElementById("scanEndInput");
|
const scanEndInput = document.getElementById("scanEndInput");
|
||||||
|
const durationInput = document.getElementById("durationInput");
|
||||||
const requestOutput = document.getElementById("requestOutput");
|
const requestOutput = document.getElementById("requestOutput");
|
||||||
const topSuccess = document.getElementById("topSuccess");
|
const topSuccess = document.getElementById("topSuccess");
|
||||||
const businessSuccess = document.getElementById("businessSuccess");
|
const businessSuccess = document.getElementById("businessSuccess");
|
||||||
@@ -813,6 +818,25 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按批量仿真接口格式组装曲柄滑块请求。
|
||||||
|
function buildSimulationRequest(input, sim, reqCode = `FOURBAR_SIM_${Date.now()}`) {
|
||||||
|
return {
|
||||||
|
msg: "fourbar crank slider simulate",
|
||||||
|
req_code: reqCode,
|
||||||
|
req_from: "fourbar_test_page",
|
||||||
|
req_cmd: "Cmd_FourBar_CrankSlider_Simulate",
|
||||||
|
req_param: {
|
||||||
|
L_AB: input.L_AB,
|
||||||
|
L_BS: input.L_BS,
|
||||||
|
S_OFS: input.S_OFS,
|
||||||
|
startAngleDeg: input.angleDeg,
|
||||||
|
endAngleDeg: sim.endAngleDeg,
|
||||||
|
duration: sim.duration,
|
||||||
|
stepTime: sim.stepTime,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 将当前表单同步到请求 JSON 文本框。
|
// 将当前表单同步到请求 JSON 文本框。
|
||||||
function refreshRequestPreview() {
|
function refreshRequestPreview() {
|
||||||
try {
|
try {
|
||||||
@@ -828,8 +852,9 @@
|
|||||||
lBsInput.value = sample.L_BS;
|
lBsInput.value = sample.L_BS;
|
||||||
sOfsInput.value = sample.S_OFS;
|
sOfsInput.value = sample.S_OFS;
|
||||||
angleInput.value = sample.angleDeg;
|
angleInput.value = sample.angleDeg;
|
||||||
stepInput.value = stepInput.value || "10";
|
stepInput.value = stepInput.value || "0.02";
|
||||||
scanEndInput.value = scanEndInput.value || "360";
|
scanEndInput.value = scanEndInput.value || "360";
|
||||||
|
durationInput.value = durationInput.value || "2";
|
||||||
refreshRequestPreview();
|
refreshRequestPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,6 +1141,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调用曲柄滑块批量仿真接口并解析 JSON 响应。
|
||||||
|
async function callFourBarSimulation(input, sim) {
|
||||||
|
const module = await ensureWasm();
|
||||||
|
const payload = JSON.stringify(buildSimulationRequest(input, sim));
|
||||||
|
let requestPtr = 0;
|
||||||
|
let responsePtr = 0;
|
||||||
|
try {
|
||||||
|
requestPtr = allocString(module, payload);
|
||||||
|
responsePtr = module._func(requestPtr);
|
||||||
|
return JSON.parse(module.UTF8ToString(responsePtr));
|
||||||
|
} finally {
|
||||||
|
if (responsePtr) module._smart_free_string(responsePtr);
|
||||||
|
if (requestPtr) module._free(requestPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算前端几何校验值,用于和 WASM 返回点位对照。
|
// 计算前端几何校验值,用于和 WASM 返回点位对照。
|
||||||
function calculateLocalGeometry(input) {
|
function calculateLocalGeometry(input) {
|
||||||
const angle = (input.angleDeg * Math.PI) / 180;
|
const angle = (input.angleDeg * Math.PI) / 180;
|
||||||
@@ -1419,43 +1460,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多角度调用接口,生成轨迹和位移曲线。
|
// 调用批量仿真接口,生成轨迹和位移曲线。
|
||||||
async function runScan() {
|
async function runScan() {
|
||||||
try {
|
try {
|
||||||
setMessage("");
|
setMessage("");
|
||||||
const baseInput = readInput();
|
const baseInput = readInput();
|
||||||
const step = Number(stepInput.value);
|
const stepTime = Number(stepInput.value);
|
||||||
const scanEnd = Number(scanEndInput.value);
|
const scanEnd = Number(scanEndInput.value);
|
||||||
if (!Number.isInteger(step) || step < 1 || step > 90) {
|
const duration = Number(durationInput.value);
|
||||||
throw new Error("扫描步长必须是 1 到 90 之间的整数");
|
if (!Number.isFinite(stepTime) || stepTime <= 0 || stepTime > 1) {
|
||||||
|
throw new Error("stepTime 必须是 0 到 1 秒之间的有效数字");
|
||||||
}
|
}
|
||||||
if (!Number.isFinite(scanEnd) || scanEnd <= 0 || scanEnd > 720) {
|
if (!Number.isFinite(scanEnd) || scanEnd <= 0 || scanEnd > 720) {
|
||||||
throw new Error("扫描终止角必须在 1 到 720 之间");
|
throw new Error("扫描终止角必须在 1 到 720 之间");
|
||||||
}
|
}
|
||||||
|
if (!Number.isFinite(duration) || duration <= 0 || duration > 60) {
|
||||||
|
throw new Error("duration 必须在 0 到 60 秒之间");
|
||||||
|
}
|
||||||
|
|
||||||
requestStatus.textContent = "扫描中";
|
requestStatus.textContent = "扫描中";
|
||||||
requestStatus.className = "badge warn";
|
requestStatus.className = "badge warn";
|
||||||
const rows = [];
|
const response = await callFourBarSimulation(baseInput, {
|
||||||
for (let angle = 0; angle <= scanEnd + 0.000001; angle += step) {
|
endAngleDeg: scanEnd,
|
||||||
const input = { ...baseInput, angleDeg: angle };
|
duration,
|
||||||
const response = await callFourBar(input, `FOURBAR_SCAN_${angle}`);
|
stepTime,
|
||||||
const data = response.res_data || {};
|
|
||||||
rows.push({
|
|
||||||
angle,
|
|
||||||
ok: response.success === true && data.success === true && !data.error,
|
|
||||||
B: data.points?.B,
|
|
||||||
S: data.points?.S,
|
|
||||||
error: data.error || response.msg || "",
|
|
||||||
});
|
});
|
||||||
|
const data = response.res_data || {};
|
||||||
|
if (response.success !== true || data.success !== true || data.error) {
|
||||||
|
throw new Error(data.error || response.msg || "批量仿真接口返回失败");
|
||||||
}
|
}
|
||||||
|
const rows = (data.frames || []).map((frame) => ({
|
||||||
|
frame: frame.frame,
|
||||||
|
time: frame.time,
|
||||||
|
angle: frame.angleDeg,
|
||||||
|
ok: Boolean(frame.points?.B && frame.points?.S),
|
||||||
|
B: frame.points?.B,
|
||||||
|
S: frame.points?.S,
|
||||||
|
error: "",
|
||||||
|
}));
|
||||||
|
|
||||||
latestScan = rows;
|
latestScan = rows;
|
||||||
scanOutput.textContent = JSON.stringify(rows, null, 2);
|
scanOutput.textContent = JSON.stringify(response, null, 2);
|
||||||
renderScanCharts(rows);
|
renderScanCharts(rows);
|
||||||
scanAnimation.frame = 0;
|
scanAnimation.frame = 0;
|
||||||
requestStatus.textContent = "扫描完成";
|
requestStatus.textContent = "扫描完成";
|
||||||
requestStatus.className = "badge ok";
|
requestStatus.className = "badge ok";
|
||||||
setMessage(`扫描完成:${rows.filter((row) => row.ok).length}/${rows.length} 个角度有效`, "is-ok");
|
setMessage(`仿真完成:${rows.filter((row) => row.ok).length}/${rows.length} 帧有效`, "is-ok");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
requestStatus.textContent = "扫描失败";
|
requestStatus.textContent = "扫描失败";
|
||||||
requestStatus.className = "badge error";
|
requestStatus.className = "badge error";
|
||||||
@@ -1472,7 +1522,7 @@
|
|||||||
scanOutput.classList.toggle("hidden", isResponse);
|
scanOutput.classList.toggle("hidden", isResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
[lAbInput, lBsInput, sOfsInput, angleInput].forEach((input) => {
|
[lAbInput, lBsInput, sOfsInput, angleInput, stepInput, scanEndInput, durationInput].forEach((input) => {
|
||||||
input.addEventListener("input", refreshRequestPreview);
|
input.addEventListener("input", refreshRequestPreview);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1495,8 +1545,9 @@
|
|||||||
scanTab.addEventListener("click", () => setActiveTab("scan"));
|
scanTab.addEventListener("click", () => setActiveTab("scan"));
|
||||||
|
|
||||||
loadSample(validSample);
|
loadSample(validSample);
|
||||||
stepInput.value = "10";
|
stepInput.value = "0.02";
|
||||||
scanEndInput.value = "360";
|
scanEndInput.value = "360";
|
||||||
|
durationInput.value = "2";
|
||||||
refreshRequestPreview();
|
refreshRequestPreview();
|
||||||
renderThreeMechanism(calculateLocalGeometry(validSample), validSample, {
|
renderThreeMechanism(calculateLocalGeometry(validSample), validSample, {
|
||||||
title: "有效样例预览",
|
title: "有效样例预览",
|
||||||
|
|||||||
@@ -401,6 +401,23 @@ const suite = [
|
|||||||
{ path: "res_data.validation_errors", lengthGte: 1 },
|
{ path: "res_data.validation_errors", lengthGte: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "fourbar_simulate",
|
||||||
|
type: "static",
|
||||||
|
requestFile: "tests/testdata/fourbar/crank_slider_simulate.json",
|
||||||
|
assertions: [
|
||||||
|
{ path: "success", equals: true },
|
||||||
|
{ path: "res_data.success", equals: true },
|
||||||
|
{ path: "res_data.frame_count", equals: 5 },
|
||||||
|
{ path: "res_data.frames", lengthEquals: 5 },
|
||||||
|
{ path: "res_data.frames.0.angleDeg", equals: 0 },
|
||||||
|
{ path: "res_data.frames.4.angleDeg", equals: 180 },
|
||||||
|
{ path: "res_data.frames.0.points.B.x", exists: true },
|
||||||
|
{ path: "res_data.frames.0.poses.TCP.tx", exists: true },
|
||||||
|
{ path: "res_data.trajectory", lengthEquals: 5 },
|
||||||
|
{ path: "res_data.slider_trajectory", lengthEquals: 5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "quadruped_points_from_motor_angles",
|
id: "quadruped_points_from_motor_angles",
|
||||||
type: "static",
|
type: "static",
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ json KinematicsWebAPI::dispatchCommand(const std::string &req_cmd, const json &r
|
|||||||
return handleSpcCommand(req_cmd, req_param);
|
return handleSpcCommand(req_cmd, req_param);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req_cmd == "Cmd_FourBar_CrankSlider")
|
if (req_cmd == "Cmd_FourBar_CrankSlider" ||
|
||||||
|
req_cmd == "Cmd_FourBar_CrankSlider_Simulate")
|
||||||
{
|
{
|
||||||
return handleFourBarCommand(req_cmd, req_param);
|
return handleFourBarCommand(req_cmd, req_param);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,82 @@
|
|||||||
#include "KinematicsWebAPI.h"
|
#include "KinematicsWebAPI.h"
|
||||||
#include "FourBarMechanism/CrankSliderMechanism.h"
|
#include "FourBarMechanism/CrankSliderMechanism.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// 将机构点位转换为接口统一 JSON。
|
||||||
|
json buildPointsJson(const MechanismState &state)
|
||||||
|
{
|
||||||
|
json points_json;
|
||||||
|
for (const auto &point_pair : state.Points)
|
||||||
|
{
|
||||||
|
points_json[point_pair.first] = {
|
||||||
|
{"x", point_pair.second.X},
|
||||||
|
{"y", point_pair.second.Y}};
|
||||||
|
}
|
||||||
|
return points_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将机构姿态转换为接口统一 JSON。
|
||||||
|
json buildPosesJson(const MechanismState &state)
|
||||||
|
{
|
||||||
|
json poses_json;
|
||||||
|
for (const auto &pose_pair : state.Poses)
|
||||||
|
{
|
||||||
|
poses_json[pose_pair.first] = {
|
||||||
|
{"tx", pose_pair.second.tx},
|
||||||
|
{"ty", pose_pair.second.ty},
|
||||||
|
{"tz", pose_pair.second.tz},
|
||||||
|
{"qx", pose_pair.second.qx},
|
||||||
|
{"qy", pose_pair.second.qy},
|
||||||
|
{"qz", pose_pair.second.qz},
|
||||||
|
{"qw", pose_pair.second.qw}};
|
||||||
|
}
|
||||||
|
return poses_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将机构角度数据转换为接口统一 JSON。
|
||||||
|
json buildAnglesJson(const MechanismState &state)
|
||||||
|
{
|
||||||
|
json angles_json;
|
||||||
|
for (const auto &angle_pair : state.Angles)
|
||||||
|
{
|
||||||
|
angles_json[angle_pair.first] = angle_pair.second;
|
||||||
|
}
|
||||||
|
return angles_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将二维点列表转换为轨迹 JSON 数组。
|
||||||
|
json buildTrajectoryJson(const std::vector<Vector2D> &points)
|
||||||
|
{
|
||||||
|
json trajectory_json = json::array();
|
||||||
|
for (const auto &point : points)
|
||||||
|
{
|
||||||
|
trajectory_json.push_back({
|
||||||
|
{"x", point.X},
|
||||||
|
{"y", point.Y}});
|
||||||
|
}
|
||||||
|
return trajectory_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装单帧机构状态,供实时接口和批量仿真接口复用。
|
||||||
|
json buildFrameJson(const MechanismState &state, int frame, double time, double angleDeg)
|
||||||
|
{
|
||||||
|
json frame_json;
|
||||||
|
frame_json["frame"] = frame;
|
||||||
|
frame_json["time"] = time;
|
||||||
|
frame_json["angleDeg"] = angleDeg;
|
||||||
|
frame_json["input_value"] = state.InputValue;
|
||||||
|
frame_json["points"] = buildPointsJson(state);
|
||||||
|
frame_json["poses"] = buildPosesJson(state);
|
||||||
|
frame_json["angles"] = buildAnglesJson(state);
|
||||||
|
if (state.hasWarning())
|
||||||
|
{
|
||||||
|
frame_json["warning"] = state.WarningMessage;
|
||||||
|
}
|
||||||
|
return frame_json;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const json &req_param)
|
json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const json &req_param)
|
||||||
{
|
{
|
||||||
@@ -11,11 +88,19 @@ json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const js
|
|||||||
double L_BS = req_param.value("L_BS", 2.0);
|
double L_BS = req_param.value("L_BS", 2.0);
|
||||||
double S_OFS = req_param.value("S_OFS", 0.0);
|
double S_OFS = req_param.value("S_OFS", 0.0);
|
||||||
double angleDeg = req_param.value("angleDeg", 0.0);
|
double angleDeg = req_param.value("angleDeg", 0.0);
|
||||||
|
double startAngleDeg = req_param.value("startAngleDeg", 0.0);
|
||||||
|
double endAngleDeg = req_param.value("endAngleDeg", 360.0);
|
||||||
|
double duration = req_param.value("duration", 2.0);
|
||||||
|
double stepTime = req_param.value("stepTime", 0.02);
|
||||||
|
|
||||||
log("Parameters: L_AB=" + std::to_string(L_AB) +
|
log("Parameters: L_AB=" + std::to_string(L_AB) +
|
||||||
", L_BS=" + std::to_string(L_BS) +
|
", L_BS=" + std::to_string(L_BS) +
|
||||||
", S_OFS=" + std::to_string(S_OFS) +
|
", S_OFS=" + std::to_string(S_OFS) +
|
||||||
", angleDeg=" + std::to_string(angleDeg));
|
", angleDeg=" + std::to_string(angleDeg) +
|
||||||
|
", startAngleDeg=" + std::to_string(startAngleDeg) +
|
||||||
|
", endAngleDeg=" + std::to_string(endAngleDeg) +
|
||||||
|
", duration=" + std::to_string(duration) +
|
||||||
|
", stepTime=" + std::to_string(stepTime));
|
||||||
|
|
||||||
std::unique_ptr<CrankSliderMechanism> mechanism(createCrankSliderMechanism());
|
std::unique_ptr<CrankSliderMechanism> mechanism(createCrankSliderMechanism());
|
||||||
mechanism->setL_AB(L_AB);
|
mechanism->setL_AB(L_AB);
|
||||||
@@ -32,6 +117,78 @@ json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const js
|
|||||||
{"validation_warnings", validation.Warnings}};
|
{"validation_warnings", validation.Warnings}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req_cmd == "Cmd_FourBar_CrankSlider_Simulate")
|
||||||
|
{
|
||||||
|
if (duration <= 0.0)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"success", false},
|
||||||
|
{"error", "Invalid simulation parameters"},
|
||||||
|
{"validation_errors", json::array({"duration 必须大于 0"})},
|
||||||
|
{"validation_warnings", json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepTime <= 0.0 || stepTime > duration)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"success", false},
|
||||||
|
{"error", "Invalid simulation parameters"},
|
||||||
|
{"validation_errors", json::array({"stepTime 必须大于 0 且不能大于 duration"})},
|
||||||
|
{"validation_warnings", json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
const int frameCount = static_cast<int>(std::ceil(duration / stepTime)) + 1;
|
||||||
|
if (frameCount < 2 || frameCount > 1000)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"success", false},
|
||||||
|
{"error", "Invalid simulation parameters"},
|
||||||
|
{"validation_errors", json::array({"仿真帧数必须在 2 到 1000 之间"})},
|
||||||
|
{"validation_warnings", json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
json frames_json = json::array();
|
||||||
|
for (int frame = 0; frame < frameCount; ++frame)
|
||||||
|
{
|
||||||
|
const double currentTime = frame == frameCount - 1 ? duration : stepTime * frame;
|
||||||
|
const double progress = duration <= 0.0 ? 0.0 : currentTime / duration;
|
||||||
|
const double currentAngleDeg = startAngleDeg + (endAngleDeg - startAngleDeg) * progress;
|
||||||
|
MechanismState state = mechanism->calculate(currentAngleDeg);
|
||||||
|
if (state.hasError())
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"success", false},
|
||||||
|
{"error", state.ErrorMessage},
|
||||||
|
{"failed_frame", frame},
|
||||||
|
{"failed_time", currentTime},
|
||||||
|
{"failed_angleDeg", currentAngleDeg}};
|
||||||
|
}
|
||||||
|
|
||||||
|
frames_json.push_back(buildFrameJson(state, frame, currentTime, currentAngleDeg));
|
||||||
|
}
|
||||||
|
|
||||||
|
json result;
|
||||||
|
result["success"] = true;
|
||||||
|
result["frames"] = frames_json;
|
||||||
|
result["frame_count"] = frameCount;
|
||||||
|
result["duration"] = duration;
|
||||||
|
result["stepTime"] = stepTime;
|
||||||
|
result["startAngleDeg"] = startAngleDeg;
|
||||||
|
result["endAngleDeg"] = endAngleDeg;
|
||||||
|
result["trajectory"] = buildTrajectoryJson(mechanism->getTrajectoryPoints());
|
||||||
|
result["slider_trajectory"] = buildTrajectoryJson(mechanism->getSliderTrajectory());
|
||||||
|
result["parameters"] = {
|
||||||
|
{"L_AB", L_AB},
|
||||||
|
{"L_BS", L_BS},
|
||||||
|
{"S_OFS", S_OFS},
|
||||||
|
{"startAngleDeg", startAngleDeg},
|
||||||
|
{"endAngleDeg", endAngleDeg},
|
||||||
|
{"duration", duration},
|
||||||
|
{"stepTime", stepTime}};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
MechanismState state = mechanism->calculate(angleDeg);
|
MechanismState state = mechanism->calculate(angleDeg);
|
||||||
if (state.hasError())
|
if (state.hasError())
|
||||||
{
|
{
|
||||||
@@ -43,35 +200,9 @@ json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const js
|
|||||||
json result;
|
json result;
|
||||||
result["success"] = true;
|
result["success"] = true;
|
||||||
|
|
||||||
json points_json;
|
result["points"] = buildPointsJson(state);
|
||||||
for (const auto &point_pair : state.Points)
|
result["poses"] = buildPosesJson(state);
|
||||||
{
|
result["angles"] = buildAnglesJson(state);
|
||||||
points_json[point_pair.first] = {
|
|
||||||
{"x", point_pair.second.X},
|
|
||||||
{"y", point_pair.second.Y}};
|
|
||||||
}
|
|
||||||
result["points"] = points_json;
|
|
||||||
|
|
||||||
json poses_json;
|
|
||||||
for (const auto &pose_pair : state.Poses)
|
|
||||||
{
|
|
||||||
poses_json[pose_pair.first] = {
|
|
||||||
{"tx", pose_pair.second.tx},
|
|
||||||
{"ty", pose_pair.second.ty},
|
|
||||||
{"tz", pose_pair.second.tz},
|
|
||||||
{"qx", pose_pair.second.qx},
|
|
||||||
{"qy", pose_pair.second.qy},
|
|
||||||
{"qz", pose_pair.second.qz},
|
|
||||||
{"qw", pose_pair.second.qw}};
|
|
||||||
}
|
|
||||||
result["poses"] = poses_json;
|
|
||||||
|
|
||||||
json angles_json;
|
|
||||||
for (const auto &angle_pair : state.Angles)
|
|
||||||
{
|
|
||||||
angles_json[angle_pair.first] = angle_pair.second;
|
|
||||||
}
|
|
||||||
result["angles"] = angles_json;
|
|
||||||
|
|
||||||
result["input_value"] = state.InputValue;
|
result["input_value"] = state.InputValue;
|
||||||
if (state.hasWarning())
|
if (state.hasWarning())
|
||||||
@@ -79,23 +210,8 @@ json KinematicsWebAPI::handleFourBarCommand(const std::string &req_cmd, const js
|
|||||||
result["warning"] = state.WarningMessage;
|
result["warning"] = state.WarningMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
json trajectory_json = json::array();
|
result["trajectory"] = buildTrajectoryJson(mechanism->getTrajectoryPoints());
|
||||||
for (const auto &point : mechanism->getTrajectoryPoints())
|
result["slider_trajectory"] = buildTrajectoryJson(mechanism->getSliderTrajectory());
|
||||||
{
|
|
||||||
trajectory_json.push_back({
|
|
||||||
{"x", point.X},
|
|
||||||
{"y", point.Y}});
|
|
||||||
}
|
|
||||||
result["trajectory"] = trajectory_json;
|
|
||||||
|
|
||||||
json slider_trajectory_json = json::array();
|
|
||||||
for (const auto &point : mechanism->getSliderTrajectory())
|
|
||||||
{
|
|
||||||
slider_trajectory_json.push_back({
|
|
||||||
{"x", point.X},
|
|
||||||
{"y", point.Y}});
|
|
||||||
}
|
|
||||||
result["slider_trajectory"] = slider_trajectory_json;
|
|
||||||
|
|
||||||
result["parameters"] = {
|
result["parameters"] = {
|
||||||
{"L_AB", L_AB},
|
{"L_AB", L_AB},
|
||||||
|
|||||||
15
tests/testdata/fourbar/crank_slider_simulate.json
vendored
Normal file
15
tests/testdata/fourbar/crank_slider_simulate.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"msg": "fourbar simulate",
|
||||||
|
"req_code": "CASE_FB_SIM_001",
|
||||||
|
"req_from": "wasm_test",
|
||||||
|
"req_cmd": "Cmd_FourBar_CrankSlider_Simulate",
|
||||||
|
"req_param": {
|
||||||
|
"L_AB": 0.5,
|
||||||
|
"L_BS": 2.0,
|
||||||
|
"S_OFS": 0.0,
|
||||||
|
"startAngleDeg": 0.0,
|
||||||
|
"endAngleDeg": 180.0,
|
||||||
|
"duration": 1.0,
|
||||||
|
"stepTime": 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user