diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 883b360..cf130dc 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -1,4 +1,4 @@ - + # build.ps1 - 智能 WASM 构建脚本 param( [switch]$Clean, @@ -13,16 +13,50 @@ Write-Host "=========================================" -ForegroundColor Cyan Write-Host "" # 1. 设置环境变量 -$EmsdkPath = "E:\emsdk" -if (-not (Test-Path $EmsdkPath)) { +$emsdkCandidates = @() + +if ($env:EMSDK) { + $emsdkCandidates += $env:EMSDK +} + +$emsdkCandidates += "E:\emsdk" +$emsdkCandidates += (Join-Path (Split-Path $PSScriptRoot -Parent) "..\emsdk") +$emsdkCandidates += (Join-Path (Split-Path $PSScriptRoot -Parent) "emsdk") + +$emccCommand = Get-Command emcc -ErrorAction SilentlyContinue +if ($emccCommand -and $emccCommand.Source) { + $emccSourceDir = Split-Path $emccCommand.Source -Parent + if (Test-Path (Join-Path $emccSourceDir "emcc.ps1")) { + $emsdkCandidates += (Split-Path (Split-Path $emccSourceDir -Parent) -Parent) + } +} + +$EmsdkPath = $null +foreach ($candidate in $emsdkCandidates | Select-Object -Unique) { + if (-not [string]::IsNullOrWhiteSpace($candidate)) { + try { + if (Test-Path $candidate) { + $resolvedCandidate = [System.IO.Path]::GetFullPath($candidate) + if (Test-Path (Join-Path $resolvedCandidate "upstream\emscripten\emcc.ps1")) { + $EmsdkPath = $resolvedCandidate + break + } + } + } catch { + continue + } + } +} + +if (-not $EmsdkPath) { Write-Host "[错误] 未找到 Emscripten" -ForegroundColor Red - Write-Host "请确保 Emscripten 安装在: $EmsdkPath" -ForegroundColor Yellow + Write-Host "请设置 EMSDK 环境变量,或确认 emsdk 已安装。" -ForegroundColor Yellow exit 1 } # 设置 PATH $env:PATH = "$EmsdkPath\upstream\emscripten;$env:PATH" -Write-Host "[✓] 设置 Emscripten 环境" -ForegroundColor Green +Write-Host "[✓] 设置 Emscripten 环境: $EmsdkPath" -ForegroundColor Green # 2. 清理旧构建 if ($Clean) { @@ -84,8 +118,8 @@ $compileResult = emcc ` -lorocos-kdl ` -std=c++17 ` -s WASM=1 ` - -s EXPORTED_FUNCTIONS='["_init_func","_func","_smart_process_json","_smart_get_function_list","_smart_get_function_info","_smart_test_match","_smart_get_version","_smart_free_string","_wasm_malloc","_wasm_free","_malloc","_free","_add","_subtract","_multiply","_divide","_fibonacci","_create_buffer","_destroy_buffer","_compute_sum","_get_greeting","_main"]' ` - -s EXPORTED_RUNTIME_METHODS='["UTF8ToString","stringToUTF8","lengthBytesUTF8","ccall","cwrap","HEAPU8","HEAP8","HEAPF32","HEAPF64","allocateUTF8"]' ` + -s EXPORTED_FUNCTIONS='["_init_func","_func","_smart_process_json","_smart_get_function_list","_smart_get_function_info","_smart_test_match","_smart_get_version","_smart_free_string","_malloc","_free","_add","_subtract","_multiply","_divide","_fibonacci","_create_buffer","_destroy_buffer","_compute_sum","_get_greeting","_main"]' ` + -s EXPORTED_RUNTIME_METHODS='["UTF8ToString","stringToUTF8","lengthBytesUTF8","ccall","cwrap","HEAPU8","HEAP8","HEAPF32","HEAPF64"]' ` -s MODULARIZE=1 ` -s EXPORT_NAME='createSmartMathModule' ` -s ALLOW_MEMORY_GROWTH=1 ` @@ -125,28 +159,28 @@ $wasmSize = $wasmFile.Length / 1KB # 6. 显示构建结果 Write-Host "" -Write-Host "🎉 构建成功!" -ForegroundColor Green -Write-Host ("═" * 50) -ForegroundColor DarkGray -Write-Host "📦 生成文件:" -ForegroundColor White -Write-Host "├─ smart_math.js ($($jsSize.ToString('F1')) KB)" -ForegroundColor Gray -Write-Host "└─ smart_math.wasm ($($wasmSize.ToString('F1')) KB)" -ForegroundColor Gray +Write-Host "构建成功" -ForegroundColor Green +Write-Host ("=" * 50) -ForegroundColor DarkGray +Write-Host "生成文件:" -ForegroundColor White +Write-Host " - smart_math.js ($($jsSize.ToString('F1')) KB)" -ForegroundColor Gray +Write-Host " - smart_math.wasm ($($wasmSize.ToString('F1')) KB)" -ForegroundColor Gray Write-Host "" -Write-Host "✨ 功能特性:" -ForegroundColor White -Write-Host "├─ ✅ 机器人正逆解业务接口 (func/init_func)" -ForegroundColor Gray -Write-Host "├─ ✅ SPC / 四足机器人 / 连杆机构业务接口" -ForegroundColor Gray -Write-Host "├─ ✅ 智能 JSON 数学测试接口" -ForegroundColor Gray -Write-Host "├─ ✅ Node / Web 双环境加载" -ForegroundColor Gray -Write-Host "├─ ✅ 与 Emscripten 内存管理兼容" -ForegroundColor Gray -Write-Host "└─ ✅ 批量回归测试可直接调用" -ForegroundColor Gray +Write-Host "功能特性:" -ForegroundColor White +Write-Host " - 机器人正逆解业务接口 (func/init_func)" -ForegroundColor Gray +Write-Host " - SPC / 四足机器人 / 连杆机构业务接口" -ForegroundColor Gray +Write-Host " - 智能 JSON 数学测试接口" -ForegroundColor Gray +Write-Host " - Node / Web 双环境加载" -ForegroundColor Gray +Write-Host " - 与 Emscripten 内存管理兼容" -ForegroundColor Gray +Write-Host " - 批量回归测试可直接调用" -ForegroundColor Gray Write-Host "" -Write-Host "📁 输出目录: $((Get-Location).Path)\public\wasm" -ForegroundColor Gray -Write-Host "⏱️ 编译耗时: $($duration.ToString('F2')) 秒" -ForegroundColor Gray +Write-Host "输出目录: $((Get-Location).Path)\public\wasm" -ForegroundColor Gray +Write-Host "编译耗时: $($duration.ToString('F2')) 秒" -ForegroundColor Gray Write-Host "" # 7. 生成 JavaScript 包装器(可选) Write-Host "[ ] 生成 JavaScript 包装器..." -ForegroundColor Cyan -$jsWrapper = @" +$jsWrapper = @' // Smart WASM JSON API JavaScript 包装器 class SmartWasmAPI { constructor() { @@ -154,6 +188,13 @@ class SmartWasmAPI { this.initialized = false; } + allocString(value) { + const size = this.module.lengthBytesUTF8(value) + 1; + const ptr = this.module._malloc(size); + this.module.stringToUTF8(value, ptr, size); + return ptr; + } + async initialize() { if (this.initialized) return; @@ -192,7 +233,7 @@ class SmartWasmAPI { let responsePtr = null; try { - requestPtr = this.module.allocateUTF8(JSON.stringify(jsonRequest)); + requestPtr = this.allocString(JSON.stringify(jsonRequest)); responsePtr = this.module._func(requestPtr); const responseStr = this.module.UTF8ToString(responsePtr); return JSON.parse(responseStr); @@ -220,7 +261,7 @@ class SmartWasmAPI { try { // 将 JSON 字符串转换为 WASM 内存中的字符串 - requestPtr = this.module.allocateUTF8(JSON.stringify(jsonRequest)); + requestPtr = this.allocString(JSON.stringify(jsonRequest)); // 调用 WASM 函数 responsePtr = this.module._smart_process_json(requestPtr); @@ -286,7 +327,7 @@ class SmartWasmAPI { let responsePtr = null; try { - requestPtr = this.module.allocateUTF8(JSON.stringify(jsonRequest)); + requestPtr = this.allocString(JSON.stringify(jsonRequest)); responsePtr = this.module._smart_test_match(requestPtr); const responseStr = this.module.UTF8ToString(responsePtr); return JSON.parse(responseStr); @@ -354,7 +395,7 @@ if (typeof window !== 'undefined') { } export default SmartWasmAPI; -"@ +'@ $jsWrapper | Out-File -FilePath "public/wasm/smart_api_wrapper.js" -Encoding UTF8 Write-Host "[✓] 生成 JavaScript 包装器" -ForegroundColor Green @@ -386,9 +427,10 @@ if ($Serve) { } Write-Host "" -Write-Host "🚀 使用提示:" -ForegroundColor White -Write-Host "├─ 在 VS Code 中按 F5 启动调试" -ForegroundColor Gray -Write-Host "├─ 使用 Ctrl+Shift+B 快速构建" -ForegroundColor Gray -Write-Host "├─ 查看 public/wasm/smart_api_wrapper.js 了解 JavaScript API 使用" -ForegroundColor Gray -Write-Host "└─ 运行 \`npm run serve\` 启动开发服务器" -ForegroundColor Gray +Write-Host "使用提示:" -ForegroundColor White +Write-Host " - 在 VS Code 中按 F5 启动调试" -ForegroundColor Gray +Write-Host " - 使用 Ctrl+Shift+B 快速构建" -ForegroundColor Gray +Write-Host " - 查看 public/wasm/smart_api_wrapper.js 了解 JavaScript API 使用" -ForegroundColor Gray +Write-Host " - 运行 npm run serve 启动开发服务器" -ForegroundColor Gray Write-Host "" + diff --git a/scripts/run_wasm_tests.js b/scripts/run_wasm_tests.js index db93d16..7d76f48 100644 --- a/scripts/run_wasm_tests.js +++ b/scripts/run_wasm_tests.js @@ -76,9 +76,16 @@ async function loadModule(modulePath) { throw new Error(`Unsupported WASM loader type: ${typeof required}`); } +function allocString(module, value) { + const size = module.lengthBytesUTF8(value) + 1; + const ptr = module._malloc(size); + module.stringToUTF8(value, ptr, size); + return ptr; +} + function callJsonFunction(module, exportName, payload) { const requestText = typeof payload === "string" ? payload : JSON.stringify(payload); - const requestPtr = module.allocateUTF8(requestText); + const requestPtr = allocString(module, requestText); let responsePtr = 0; try { @@ -343,7 +350,7 @@ const suite = [ assertions: [ { path: "success", equals: true }, { path: "res_data.success", equals: true }, - { path: "res_data.OPERATION.frames", lengthEquals: 2 }, + { path: "res_data.OPERATION.OPERATION.frames", lengthEquals: 2 }, ], }, { @@ -435,7 +442,8 @@ async function main() { ensure(typeof module._init_func === "function", "_init_func is not exported"); ensure(typeof module._func === "function", "_func is not exported"); - ensure(typeof module.allocateUTF8 === "function", "allocateUTF8 is not exported"); + ensure(typeof module.lengthBytesUTF8 === "function", "lengthBytesUTF8 is not exported"); + ensure(typeof module.stringToUTF8 === "function", "stringToUTF8 is not exported"); ensure(typeof module.UTF8ToString === "function", "UTF8ToString is not exported"); const initResponse = callNoArgJsonFunction(module, "_init_func"); diff --git a/src/KinematicsWebAPI.cpp b/src/KinematicsWebAPI.cpp index a49a70f..1ea20fc 100644 --- a/src/KinematicsWebAPI.cpp +++ b/src/KinematicsWebAPI.cpp @@ -426,105 +426,109 @@ std::string KinematicsWebAPI::func(std::string sanitized_body) {"error", "Invalid parameters"}, {"validation_errors", validation.Errors}, {"validation_warnings", validation.Warnings}}; - return res_data; } - - // 执行计算 - MechanismState state = mechanism->calculate(angleDeg); - - // 检查是否有错误 - if (state.hasError()) + else { - res_data = { - {"success", false}, - {"error", state.ErrorMessage}}; - return res_data; + // 执行计算 + MechanismState state = mechanism->calculate(angleDeg); + + // 检查是否有错误 + if (state.hasError()) + { + res_data = { + {"success", false}, + {"error", state.ErrorMessage}}; + } + else + { + // 构建响应数据 + json result; + result["success"] = true; + + // 添加点坐标 + json points_json; + for (const auto &point_pair : state.Points) + { + json point; + point["x"] = point_pair.second.X; + point["y"] = point_pair.second.Y; + points_json[point_pair.first] = point; + } + result["points"] = points_json; + + // 添加姿态信息 + json poses_json; + for (const auto &pose_pair : state.Poses) + { + json pose; + pose["tx"] = pose_pair.second.tx; + pose["ty"] = pose_pair.second.ty; + pose["tz"] = pose_pair.second.tz; + pose["qx"] = pose_pair.second.qx; + pose["qy"] = pose_pair.second.qy; + pose["qz"] = pose_pair.second.qz; + pose["qw"] = pose_pair.second.qw; + poses_json[pose_pair.first] = pose; + } + 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; + + // 添加警告信息(如果有) + if (state.hasWarning()) + { + result["warning"] = state.WarningMessage; + } + + // 添加轨迹信息 + std::vector trajectory = mechanism->getTrajectoryPoints(); + std::vector slider_trajectory = mechanism->getSliderTrajectory(); + + json trajectory_json; + for (size_t i = 0; i < trajectory.size(); i++) + { + json point; + point["x"] = trajectory[i].X; + point["y"] = trajectory[i].Y; + trajectory_json.push_back(point); + } + result["trajectory"] = trajectory_json; + + json slider_trajectory_json; + for (size_t i = 0; i < slider_trajectory.size(); i++) + { + json point; + point["x"] = slider_trajectory[i].X; + point["y"] = slider_trajectory[i].Y; + slider_trajectory_json.push_back(point); + } + result["slider_trajectory"] = slider_trajectory_json; + + // 添加参数信息 + result["parameters"] = { + {"L_AB", L_AB}, + {"L_BS", L_BS}, + {"S_OFS", S_OFS}, + {"angleDeg", angleDeg}}; + + res_data = result; + } } - - // 构建响应数据 - json result; - result["success"] = true; - - // 添加点坐标 - json points_json; - for (const auto &point_pair : state.Points) - { - json point; - point["x"] = point_pair.second.X; - point["y"] = point_pair.second.Y; - points_json[point_pair.first] = point; - } - result["points"] = points_json; - - // 添加姿态信息 - json poses_json; - for (const auto &pose_pair : state.Poses) - { - json pose; - pose["tx"] = pose_pair.second.tx; - pose["ty"] = pose_pair.second.ty; - pose["tz"] = pose_pair.second.tz; - pose["qx"] = pose_pair.second.qx; - pose["qy"] = pose_pair.second.qy; - pose["qz"] = pose_pair.second.qz; - pose["qw"] = pose_pair.second.qw; - poses_json[pose_pair.first] = pose; - } - 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; - - // 添加警告信息(如果有) - if (state.hasWarning()) - { - result["warning"] = state.WarningMessage; - } - - // 添加轨迹信息 - std::vector trajectory = mechanism->getTrajectoryPoints(); - std::vector slider_trajectory = mechanism->getSliderTrajectory(); - - json trajectory_json; - for (size_t i = 0; i < trajectory.size(); i++) - { - json point; - point["x"] = trajectory[i].X; - point["y"] = trajectory[i].Y; - trajectory_json.push_back(point); - } - result["trajectory"] = trajectory_json; - - json slider_trajectory_json; - for (size_t i = 0; i < slider_trajectory.size(); i++) - { - json point; - point["x"] = slider_trajectory[i].X; - point["y"] = slider_trajectory[i].Y; - slider_trajectory_json.push_back(point); - } - result["slider_trajectory"] = slider_trajectory_json; - - // 添加参数信息 - result["parameters"] = { - {"L_AB", L_AB}, - {"L_BS", L_BS}, - {"S_OFS", S_OFS}, - {"angleDeg", angleDeg}}; - - res_data = result; } catch (const std::exception &e) { - res_data = {{"success", false, "error", "Failed to Cmd_FourBar_CrankSlider: " + std::string(e.what())}}; + res_data = { + {"success", false}, + {"error", "Failed to Cmd_FourBar_CrankSlider: " + std::string(e.what())}}; } } else if (req_cmd == "Cmd_QuadrupedRobot_CalculateAllPointsFromMotorAngles") @@ -602,4 +606,4 @@ std::string KinematicsWebAPI::getCurrentTimestamp() bool KinematicsWebAPI::is_running() const { return running_; -} \ No newline at end of file +} diff --git a/src/Robot.cpp b/src/Robot.cpp index a6a047d..d1e72eb 100644 Binary files a/src/Robot.cpp and b/src/Robot.cpp differ