feat: 后置高刚性防倾翻测试台架与J1电机镂空散热防拉脱托架整合 (P01/P02/P03重构)
@@ -1,23 +1,98 @@
|
||||
# 机器人手臂(6自由度机械臂设计)
|
||||
# 🤖 机器人手臂(6自由度参数化机械臂工程)
|
||||
|
||||
基于 CadQuery 的参数化机械臂建模项目,包含建模源码、二维图纸、力矩/强度校核与浏览器 3D 查看器。
|
||||
基于 [CadQuery](https://github.com/CadQuery/cadquery) 的参数化 6 自由度人形机械臂建模工程。对标智元灵犀 X1 构型,采用 6 颗 60mm 标准减速电机模组,支持全参数化建模、动态全域碰撞检测、制造文件(STEP/STL)导出及单文件离线三维交互查看器。
|
||||
|
||||
## 目录结构
|
||||
---
|
||||
|
||||
- `src/` — CadQuery 建模与分析脚本
|
||||
- `arm_model.py` / `arm_build.py` — 机械臂建模主脚本
|
||||
- `armcore.py` / `filletlib.py` — 建模基础库
|
||||
- `strength_check.py` / `torque_balance.py` / `travel_sweep.py` — 强度、力矩、行程校核
|
||||
- `make_*.py` — 二维图纸/示意图生成
|
||||
- `viewer_tpl.html` / `build_viewer.py` — 3D 查看器模板与构建
|
||||
- `docs/` — 图纸(SVG)、渲染图(PNG)、BOM、力矩报告等设计文档
|
||||
- `web/` — 生成的浏览器 3D 查看器(`机器人手臂3D查看器.html`)
|
||||
- `sop_extracted/` — 从 SOP 文档中提取的参考图片
|
||||
- `out/` — 构建产物(STEP/STL),不入库,可由 `src/` 重新生成
|
||||
- `scratch/` — 临时调试脚本,不入库
|
||||
## 🌟 核心设计特性
|
||||
|
||||
## 用法
|
||||
1. **一体化直锁电机**:全面取消外挂独立端面齿联轴器,所有关节由 3×M2 沉头螺钉直锁电机输出轴,轴向尺寸大幅压缩,刚度显著提升。
|
||||
2. **五级流线圆角系统**:建立统一的有机曲面过渡规范(R=1.5mm 微圆角 ~ R=15mm 视觉主导大圆角),彻底消除锐边与机械生硬台阶。
|
||||
3. **全行程动态零干涉**:经 `sweep_v2.py` 多进程全域扫描与二分逼近验证,各自由度极限角度下实现 0 碰撞。
|
||||
4. **100% 流形单实体**:9/9 构件均为无缝单一实体拓扑,完全杜绝浮动壳体与布尔破碎,满足 FDM/SLA 打印及 CNC 加工要求。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速上手
|
||||
|
||||
### 1. 编译并导出最新模型
|
||||
一键重新生成全部 9 个打印构件、装配体、STEP/STL 制造文件及 3D 查看器数据:
|
||||
```bash
|
||||
python src/arm_model.py # 生成模型与 out/ 下的导出文件
|
||||
python src/arm_model.py
|
||||
```
|
||||
> 导出的 STEP 文件保存在 `out/parts_step/`,STL 保存在 `out/print_stl/`,圆角专用版保存在 `out/v2_rounded/`。
|
||||
|
||||
### 2. 全行程动态防碰撞扫描
|
||||
多进程并行扫描全关节运动极限硬边界,并更新滑动安全范围:
|
||||
```bash
|
||||
python src/sweep_v2.py
|
||||
```
|
||||
|
||||
### 3. 三维交互查看器
|
||||
- **使用方式**:直接双击打开项目根目录下的 **`机器人手臂3D查看器.html`** 即可在任意浏览器全屏交互查看(纯本地离线单文件,无需任何外网或 CDN)。
|
||||
- 支持:全自由度姿态滑块调节、部件爆炸图、单零件孤立透视、应力与干涉显示。
|
||||
|
||||
---
|
||||
|
||||
## 📁 项目目录结构
|
||||
|
||||
```
|
||||
机器人手臂/
|
||||
├── 机器人手臂3D查看器.html # 单文件内置 3D 查看器(双击即用)
|
||||
├── README.md # 本说明文档
|
||||
├── src/ # 核心源码目录
|
||||
│ ├── arm_model.py # ⭐ 唯一权威主力建模脚本(含五级圆角与最新几何)
|
||||
│ ├── sweep_v2.py # ⭐ 权威动态碰撞检测与行程扫描工具
|
||||
│ ├── arm_build.py # 底层几何基础库与通用装配/导出函数
|
||||
│ ├── build_viewer.py # 3D 查看器打包生成脚本
|
||||
│ ├── human_layout.py # 机械臂各关节运动学拓扑与唯一事实参数源
|
||||
│ ├── filletlib.py # 圆角与倒角工具函数
|
||||
│ ├── strength_check.py # 刚度与应力分析
|
||||
│ ├── torque_balance.py # 关节力矩平衡校核
|
||||
│ ├── arm_model_v2_rounded.py # 兼容重定向文件(导向 arm_model.py)
|
||||
│ └── legacy/ # 历史演进版本备份目录(h1 初版、h2 优化版)
|
||||
├── docs/ # 详细技术报告与工程图纸
|
||||
│ ├── optimization_report.md # 几何优化综合报告
|
||||
│ ├── optimization_technical_details.md # 圆角与曲面数学实现详解
|
||||
│ ├── optimization_usage_guide.md # 3D 打印与装配实操指南
|
||||
│ ├── 动态碰撞测试报告.md # 动态行程扫描与硬限位测试明细
|
||||
│ ├── travel.json # 各关节实测行程与软限位配置
|
||||
│ ├── bom.json # 零件明细与质量属性表
|
||||
│ └── history_reports/ # 历史各阶段迭代交付总结归档
|
||||
├── web/ # 查看器底层模板与几何网格数据 (mesh_arm.json)
|
||||
└── out/ # 编译制造产物
|
||||
├── parts_step/ # 各零件标准 STEP 实体
|
||||
├── print_stl/ # 各零件 3D 打印 STL 文件
|
||||
├── v2_rounded/ # 深度圆角零件文件
|
||||
└── assembly/ # 整臂装配体 STEP (含 ROBOT_ARM.stp)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🦾 机械臂构件清单 (P01 ~ P09)
|
||||
|
||||
| 构件编号 | 构件名称 | 关联驱动关节 | 设计说明 |
|
||||
| :--- | :--- | :---: | :--- |
|
||||
| **P01** | 胸板 / 安装板 | 根基固定 | 12mm 基础基座,预留 4x M4 紧固位与躯干走线窗 |
|
||||
| **P02** | 底脚 / 支撑座 | 根基固定 | 200×320mm 重型防倾覆底盘,提供稳固调试基座 |
|
||||
| **P03** | 肩吊臂 / 肩机舱 | J1 驱动抱紧 | 仿生规整风道机舱,带 7 组轴向散热格栅,抱紧 M1 电机 |
|
||||
| **P04** | 肩叉 | J1 ➔ J2 | 90° 一体化高刚度正交转向肘,直锁 M1,抱紧 M2 |
|
||||
| **P05** | 大臂旋转座 | J2 ➔ J3 | 16 层 Loft 曲面平滑过渡,直锁 M2,后侧刚性抱紧 M3 |
|
||||
| **P06** | 大臂骨架 | J3 ➔ J4 | 流线型工字背脊骨架,直锁 M3,下端抱紧 M4 肘电机 |
|
||||
| **P07** | 小臂骨架 | J4 ➔ J5 | L 型圆弧强化立柱,大范围自由运动避让,抱紧 M5 |
|
||||
| **P08** | 腕偏转座 | J5 ➔ J6 | 16 层 Loft 双曲面过渡,直锁 M5,立式抱紧 M6 腕旋转电机 |
|
||||
| **P09** | 工具法兰 | J6 输出 | 直锁 M6 输出轴,标准工业 ISO 法兰(4x M4 螺孔 BCD 40mm) |
|
||||
|
||||
---
|
||||
|
||||
## 📚 详细文档导航
|
||||
|
||||
- 📘 **设计与制造**:
|
||||
- [几何优化技术详解](file:///g:/Robot_v2/机器人手臂/docs/optimization_technical_details.md)
|
||||
- [3D打印参数与装配指南](file:///g:/Robot_v2/机器人手臂/docs/optimization_usage_guide.md)
|
||||
- [力矩平衡与承载报告](file:///g:/Robot_v2/机器人手臂/docs/torque_report.md)
|
||||
- 🔬 **测试与校验**:
|
||||
- [动态防碰撞扫描报告](file:///g:/Robot_v2/机器人手臂/docs/动态碰撞测试报告.md)
|
||||
- [零件干涉修复记录](file:///g:/Robot_v2/机器人手臂/docs/零件干涉修复报告.md)
|
||||
- 📦 **历史迭代总结归档**:
|
||||
- [docs/history_reports/](file:///g:/Robot_v2/机器人手臂/docs/history_reports/)(包含以往全部各阶段对比与完成总结)
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
[
|
||||
{
|
||||
"件号": "P01_胸板",
|
||||
"名称": "胸腔支撑立柱(V2高刚性箱型梁+榫卯台座+工艺走线窗)",
|
||||
"名称": "胸腔支撑立柱(高刚性箱型梁+榫卯台座+走线窗)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 847.7,
|
||||
"质量g_PLA": 1051.2,
|
||||
"包络": "[-60.0 14.0 22.0 .. 6.0 80.0 470.0] (66.0x66.0x448.0)",
|
||||
"体积cm3": 809.6,
|
||||
"质量g_PLA": 1003.9,
|
||||
"包络": "[-148.0 95.0 22.0 .. -82.0 161.0 470.0] (66.0x66.0x448.0)",
|
||||
"说明": "V2高刚度立柱:66x66mm厚壁封闭箱梁+8mm壁厚+顶部P03榫卯台座+两组大圆角工艺走线窗"
|
||||
},
|
||||
{
|
||||
"件号": "P02_底脚",
|
||||
"名称": "测试台架底座(V2高刚度抗倾覆底盘+嵌装定位槽)",
|
||||
"名称": "测试台架底座(高刚度抗倾覆底盘+嵌装定位槽)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 1305.9,
|
||||
"质量g_PLA": 1619.3,
|
||||
"包络": "[-121.3 -91.3 -0.0 .. 81.3 231.3 22.0] (202.6x322.6x22.0)",
|
||||
"体积cm3": 1166.9,
|
||||
"质量g_PLA": 1446.9,
|
||||
"包络": "[-186.3 16.7 -0.0 .. 76.3 239.3 22.0] (262.6x222.6x22.0)",
|
||||
"说明": "V2加长大底盘:200x320mm重型防倾覆底座+12mm嵌装槽+4xM6夹持槽+底部走线槽"
|
||||
},
|
||||
{
|
||||
"件号": "P03_肩吊臂",
|
||||
"名称": "肩关节基座机壳(V2高刚性规整风道散热版)",
|
||||
"名称": "肩关节基座机壳(高刚性规整风道散热版)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 386.5,
|
||||
"质量g_PLA": 479.2,
|
||||
"包络": "[-59.0 16.0 455.0 .. 5.0 167.5 649.5] (64.0x151.5x194.5)",
|
||||
"体积cm3": 745.3,
|
||||
"质量g_PLA": 924.2,
|
||||
"包络": "[-148.0 91.0 455.0 .. 2.0 165.0 647.0] (150.0x74.0x192.0)",
|
||||
"说明": "V2浑然一体散热版:Ø73mm机舱+7组轴向散热格栅+PCBA对流窗+一体化顺滑斜挑立柱"
|
||||
},
|
||||
{
|
||||
"件号": "P04_肩叉",
|
||||
"名称": "肩叉(V2深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
|
||||
"名称": "肩叉(深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 347.3,
|
||||
"质量g_PLA": 430.7,
|
||||
"包络": "[-16.5 90.0 455.0 .. 53.5 166.0 648.0] (70.0x76.0x193.0)",
|
||||
"说明": "V2圆角:顶部方形突起改为R=8mm圆弧过渡,圆盘边缘R=10mm大圆角"
|
||||
"说明": "V2圆角:顶部方形突起改为R=8mm圆弧过渡,圆盘边缘R=10mm大圆角;散热腔顶板改为与前后安装环同一R=35圆柱凹弧(J2轴心),腔体保持敞开散热"
|
||||
},
|
||||
{
|
||||
"件号": "P05_大臂旋转座",
|
||||
"名称": "大臂旋转座(V2深度圆角版:16层Loft+连接处R=15mm)",
|
||||
"名称": "大臂旋转座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 162.9,
|
||||
"质量g_PLA": 202.0,
|
||||
"包络": "[-16.5 93.0 389.0 .. 53.5 174.0 525.0] (70.0x81.0x136.0)",
|
||||
"说明": "V2圆角:Loft外轮廓R=10mm,后骨架与法兰连接处R=15mm超大圆角"
|
||||
"说明": "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J2全角度0干涉"
|
||||
},
|
||||
{
|
||||
"件号": "P06_大臂骨架",
|
||||
"名称": "大臂骨架(V2深度圆角版:大圆弧收腰+外轮廓R=10mm)",
|
||||
"名称": "大臂骨架(平整版+前侧圆形过渡:顶部R10.5圆弧卷入)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 165.8,
|
||||
"质量g_PLA": 205.5,
|
||||
"体积cm3": 182.9,
|
||||
"质量g_PLA": 226.8,
|
||||
"包络": "[-16.5 91.8 297.0 .. 53.5 163.0 390.1] (70.0x71.2x93.1)",
|
||||
"说明": "V2圆角:XZ大圆弧零台阶,外轮廓R=10mm,过渡区R=6mm"
|
||||
"说明": "平整版:XZ两侧竖直直面,前侧上段竖直面+R10.5圆形过渡(净空P07立柱带3mm,J4不再卡死),电机散热腔+M4/J3接口"
|
||||
},
|
||||
{
|
||||
"件号": "P07_小臂骨架",
|
||||
"名称": "小臂骨架(V2 L型高刚度折角版:+Y立柱+折角刚性连接+半包散热)",
|
||||
"名称": "小臂骨架(L型圆弧加强版:R=28mm双相切内凹大圆弧+消除悬空块)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 176.8,
|
||||
"质量g_PLA": 219.3,
|
||||
"体积cm3": 173.6,
|
||||
"质量g_PLA": 215.3,
|
||||
"包络": "[-16.5 92.0 187.0 .. 53.5 174.0 367.0] (70.0x82.0x180.0)",
|
||||
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,折角刚性过渡,下方M5电机半包散热,俯仰大范围自由运动零干涉"
|
||||
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶面和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,下方M5电机半包散热,俯仰大范围自由运动零干涉"
|
||||
},
|
||||
{
|
||||
"件号": "P08_腕偏转座",
|
||||
"名称": "腕部转接座(V2深度圆角版:16层Loft+连接处R=15mm)",
|
||||
"名称": "腕部转接座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 163.1,
|
||||
"质量g_PLA": 202.3,
|
||||
"包络": "[-16.5 93.0 120.0 .. 53.5 174.0 257.0] (70.0x81.0x137.0)",
|
||||
"说明": "V2圆角:Loft外轮廓R=10mm,后骨架与法兰连接处R=15mm超大圆角"
|
||||
"说明": "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉"
|
||||
},
|
||||
{
|
||||
"件号": "P09_工具法兰",
|
||||
"名称": "末端工具法兰(V2深度圆角版:外轮廓R=3mm)",
|
||||
"名称": "末端工具法兰(深度圆角版:外轮廓R=3mm)",
|
||||
"类型": "3D打印",
|
||||
"数量": 1,
|
||||
"体积cm3": 98.8,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
================================================================================
|
||||
机器人手臂深度圆角优化版 V2(含网页数据导出)
|
||||
开始时间: 2026-09-27 03:58:39
|
||||
================================================================================
|
||||
|
||||
零件完整性检查:
|
||||
P01_胸板 847.7 cm3 solids=1
|
||||
P02_底脚 1305.9 cm3 solids=1
|
||||
P03_肩吊臂 386.5 cm3 solids=1
|
||||
P04_肩叉 327.7 cm3 solids=1
|
||||
P05_大臂旋转座 150.7 cm3 solids=1
|
||||
P06_大臂骨架 166.0 cm3 solids=1
|
||||
P07_小臂骨架 175.8 cm3 solids=1
|
||||
P08_腕偏转座 151.2 cm3 solids=1
|
||||
P09_工具法兰 98.8 cm3 solids=1
|
||||
单实体失败数: 0
|
||||
|
||||
STEP/STL + web/mesh_arm.json 导出完成
|
||||
COLLISION pairs=0
|
||||
viewer G:/Robot_v2/机器人手臂/web/机器人手臂3D查看器.html (and in G:/Robot_v2/机器人手臂) 15.24 MB (three 652 KB + data 14148 KB + sheets 51 KB)
|
||||
3D 查看器网页更新完成!
|
||||
完成时间: 2026-09-27 03:59:49
|
||||
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 105 KiB |
@@ -0,0 +1,20 @@
|
||||
==============================================================================
|
||||
全对全动态行程扫描 v2 并行版(workers=12 / 可用内存 72GB / 轴=human_layout / 含相邻对与电机 / GJK预筛=开)
|
||||
==============================================================================
|
||||
[零位检查] 0 对干涉 ✓
|
||||
|
||||
[单关节硬限位] 粗扫 2.0° + 二分 0.2°
|
||||
J1 正向 24/90 角度(27%)[已用 13s]
|
||||
J1 正向 48/90 角度(53%)[已用 22s]
|
||||
J1 正向 72/90 角度(80%)[已用 31s]
|
||||
J1 正向 90/90 角度(100%)[已用 39s]
|
||||
J1 反向 24/90 角度(27%)[已用 160s]
|
||||
J1 反向在 -2.0° 首次干涉:P01_胸板xP07_小臂骨架(763mm3); P01_胸板xP09_工具法兰(12mm3)
|
||||
J1 二分#1 区间 [0.00, -2.00]°
|
||||
J1 二分#2 区间 [-1.00, -2.00]°
|
||||
J1 二分#3 区间 [-1.50, -2.00]°
|
||||
J1 二分#4 区间 [-1.50, -1.75]°
|
||||
=> J1 硬限位 [ -1.62, 180.00],耗时 194s
|
||||
J1 lo= -1.62 hi= 180.00 卡死: -2.0°:P01_胸板xP07_小臂骨架@(-5.8, 79.6, 224.8)(763),P01_胸板xP09_工具法兰@(2.5, 79.1, 103.2)(12) [529s]
|
||||
J2 正向 24/90 角度(27%)[已用 342s]
|
||||
J2 正向 48/90 角度(53%)[已用 719s]
|
||||
@@ -0,0 +1,34 @@
|
||||
==============================================================================
|
||||
全对全动态行程扫描 v2 并行版(workers=12 / 可用内存 64GB / 轴=human_layout / 含相邻对与电机 / GJK预筛=开)
|
||||
==============================================================================
|
||||
[零位检查] 0 对干涉 ✓
|
||||
|
||||
[单关节硬限位] 粗扫 2.0° + 二分 0.2°
|
||||
J1 正向 24/90 角度(27%)[已用 15s]
|
||||
J1 正向 48/90 角度(53%)[已用 26s]
|
||||
J1 正向 72/90 角度(80%)[已用 37s]
|
||||
J1 正向 90/90 角度(100%)[已用 47s]
|
||||
J1 反向 24/90 角度(27%)[已用 182s]
|
||||
J1 反向在 -2.0° 首次干涉:P01_胸板xP07_小臂骨架(763mm3); P01_胸板xP09_工具法兰(12mm3)
|
||||
J1 二分#1 区间 [4.00, -2.00]°
|
||||
J1 二分#2 区间 [1.00, -2.00]°
|
||||
J1 二分#3 区间 [-0.50, -2.00]°
|
||||
J1 二分#4 区间 [-1.25, -2.00]°
|
||||
J1 二分#5 区间 [-1.62, -2.00]°
|
||||
=> J1 硬限位 [ -1.62, 180.00],耗时 233s
|
||||
J1 lo= -1.62 hi= 180.00 卡死: -2.0°:P01_胸板xP07_小臂骨架@(-5.8, 79.6, 224.8)(763),P01_胸板xP09_工具法兰@(2.5, 79.1, 103.2)(12) [446s]
|
||||
J2 正向 24/90 角度(27%)[已用 51s]
|
||||
J2 正向 48/90 角度(53%)[已用 100s]
|
||||
J2 正向 72/90 角度(80%)[已用 306s]
|
||||
J2 正向在 116.0° 首次干涉:P03_肩吊臂xP06_大臂骨架(47mm3)
|
||||
J2 二分#1 区间 [114.00, 116.00]°
|
||||
J2 二分#2 区间 [115.00, 116.00]°
|
||||
J2 二分#3 区间 [115.00, 115.50]°
|
||||
J2 二分#4 区间 [115.00, 115.25]°
|
||||
J2 反向 24/90 角度(27%)[已用 421s]
|
||||
J2 反向 48/90 角度(53%)[已用 461s]
|
||||
J2 反向 72/90 角度(80%)[已用 528s]
|
||||
J2 反向在 -118.0° 首次干涉:P04_肩叉xP05_大臂旋转座(157mm3)
|
||||
J2 二分#1 区间 [120.00, -118.00]°
|
||||
J2 二分#2 区间 [1.00, -118.00]°
|
||||
J2 二分#3 区间 [-58.50, -118.00]°
|
||||
@@ -1,43 +1,43 @@
|
||||
[
|
||||
{
|
||||
"axis": "J1",
|
||||
"lo": -2.0,
|
||||
"lo": 0.0,
|
||||
"hi": 179.0,
|
||||
"continuous": false,
|
||||
"zero": 0.0,
|
||||
"note": "正向 +179° / 反向 -2°,总行程 181°(全对全动态扫描验证,含相邻件与电机)"
|
||||
"note": "正向 +179° / 反向 0°,总行程 179°(全对全动态扫描验证,含相邻件与电机)"
|
||||
},
|
||||
{
|
||||
"axis": "J2",
|
||||
"lo": -112.0,
|
||||
"hi": 116.0,
|
||||
"lo": -114.0,
|
||||
"hi": 113.0,
|
||||
"continuous": false,
|
||||
"zero": 0.0,
|
||||
"note": "正向 +116° / 反向 -112°,总行程 228°(全对全动态扫描验证,含相邻件与电机)"
|
||||
"note": "正向 +113° / 反向 -114°,总行程 227°(全对全动态扫描验证,含相邻件与电机)"
|
||||
},
|
||||
{
|
||||
"axis": "J3",
|
||||
"lo": -180.0,
|
||||
"hi": 180.0,
|
||||
"continuous": true,
|
||||
"lo": -108.0,
|
||||
"hi": 152.0,
|
||||
"continuous": false,
|
||||
"zero": 0.0,
|
||||
"note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)"
|
||||
"note": "正向 +152° / 反向 -108°,总行程 260°(全对全动态扫描验证,含相邻件与电机)"
|
||||
},
|
||||
{
|
||||
"axis": "J4",
|
||||
"lo": -105.0,
|
||||
"hi": 105.0,
|
||||
"lo": -104.0,
|
||||
"hi": 104.0,
|
||||
"continuous": false,
|
||||
"zero": 0.0,
|
||||
"note": "正向 +105° / 反向 -105°,总行程 210°(L型骨架消除轴向干涉,行程近翻倍)"
|
||||
"note": "正向 +104° / 反向 -104°,总行程 208°(全对全动态扫描验证,含相邻件与电机)"
|
||||
},
|
||||
{
|
||||
"axis": "J5",
|
||||
"lo": -101.0,
|
||||
"hi": 101.0,
|
||||
"lo": -76.0,
|
||||
"hi": 76.0,
|
||||
"continuous": false,
|
||||
"zero": 0.0,
|
||||
"note": "正向 +101° / 反向 -101°,总行程 202°(全对全动态扫描验证,含相邻件与电机)"
|
||||
"note": "正向 +76° / 反向 -76°,总行程 152°(全对全动态扫描验证,含相邻件与电机)"
|
||||
},
|
||||
{
|
||||
"axis": "J6",
|
||||
|
||||
@@ -584,14 +584,27 @@ def write_step(items, path):
|
||||
return os.path.getsize(path)
|
||||
|
||||
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
def export(JS):
|
||||
for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
print("=" * 80, flush=True)
|
||||
print("【第二阶段 2/5】并行导出 STEP / STL 制造模型 (out/parts_step, out/print_stl)...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
|
||||
# 1. 多线程并行导出 STEP 与 STL(复用已有标准电机 STEP,极速提速)
|
||||
printed_parts = [p for p in PARTS if p["printed"]]
|
||||
total_exp = len(printed_parts)
|
||||
done_exp = 0
|
||||
exp_lock = threading.Lock()
|
||||
|
||||
def export_one_part(p):
|
||||
nonlocal done_exp
|
||||
t0 = time.time()
|
||||
fn = "%s/%s.step" % (OUT + "/parts_step", p["name"])
|
||||
# 如果是非打印标准已有件且已存在,跳过冗余写盘
|
||||
if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000:
|
||||
@@ -601,17 +614,39 @@ def export(JS):
|
||||
if p["printed"]:
|
||||
stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"])
|
||||
cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL")
|
||||
with exp_lock:
|
||||
done_exp += 1
|
||||
dt = time.time() - t0
|
||||
print(f" [文件导出 {done_exp}/{total_exp}] {p['name']}.step / .stl 导出完成 ({dt:.2f}s) ✓", flush=True)
|
||||
return p["name"]
|
||||
|
||||
workers = min(8, os.cpu_count() or 4)
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
list(ex.map(export_one_part, PARTS))
|
||||
|
||||
print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True)
|
||||
|
||||
# 2. 多线程并行生成网页 3D Mesh
|
||||
print("=" * 80, flush=True)
|
||||
print("【第三阶段 3/5】多线程并行生成网页 3D 轻量化几何网格 (web/mesh_arm.json)...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
|
||||
done_mesh = 0
|
||||
mesh_lock = threading.Lock()
|
||||
total_parts = len(PARTS)
|
||||
|
||||
def mesh_one_part(p):
|
||||
nonlocal done_mesh
|
||||
t0 = time.time()
|
||||
key = "motor" if p["name"].startswith("M") else ("coupl" if "联轴器" in p["name"] or "抱箍" in p["name"] else p["name"])
|
||||
defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055)
|
||||
v, t = mesh_of(p["shape"], defl)
|
||||
with mesh_lock:
|
||||
done_mesh += 1
|
||||
n_v = len(v) // 3
|
||||
n_t = len(t) // 3
|
||||
dt = time.time() - t0
|
||||
print(f" [网格细分 {done_mesh:2d}/{total_parts}] {p['name']:<14} 顶点: {n_v:5d} | 面片: {n_t:5d} ({dt:.2f}s) ✓", flush=True)
|
||||
return (p, v, t)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
@@ -637,25 +672,49 @@ def export(JS):
|
||||
数量=1, 体积cm3=round(p["vol"] / 1000.0, 1),
|
||||
质量g_PLA=round(p["vol"] * 1.24e-3, 1), 包络=p["bb"], 说明=p["note"]) for p in PARTS]
|
||||
json.dump(bom, open(DOCS + "/bom.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(f"-> 3D网格与BOM清单已保存至 web/mesh_arm.json 与 docs/bom.json ✓\n", flush=True)
|
||||
return bom
|
||||
|
||||
|
||||
def collide():
|
||||
ps = [p for p in PARTS if p["vol"] > 1.0]
|
||||
pairs = []
|
||||
MOUNT_PAIRS = {
|
||||
("P03_肩吊臂", "M1_J1"), ("P04_肩叉", "M1_J1"),
|
||||
("P04_肩叉", "M2_J2"), ("P05_大臂旋转座", "M2_J2"),
|
||||
("P05_大臂旋转座", "M3_J3"), ("P06_大臂骨架", "M3_J3"),
|
||||
("P06_大臂骨架", "M4_J4"), ("P07_小臂骨架", "M4_J4"),
|
||||
("P07_小臂骨架", "M5_J5"), ("P08_腕偏转座", "M5_J5"),
|
||||
("P08_腕偏转座", "M6_J6"), ("P09_工具法兰", "M6_J6"),
|
||||
}
|
||||
for i in range(len(ps)):
|
||||
for j in range(i + 1, len(ps)):
|
||||
a, b = ps[i], ps[j]
|
||||
if a["name"].startswith("M") and b["name"].startswith("M"):
|
||||
continue
|
||||
if (a["name"], b["name"]) in MOUNT_PAIRS or (b["name"], a["name"]) in MOUNT_PAIRS:
|
||||
continue
|
||||
av, bv = bb(a["shape"]), bb(b["shape"])
|
||||
if any(av[1][k] < bv[0][k] - 0.05 or bv[1][k] < av[0][k] + 0.05 for k in range(3)):
|
||||
continue
|
||||
pairs.append((a, b))
|
||||
|
||||
print("=" * 80, flush=True)
|
||||
print(f"【第四阶段 4/5】多线程精确布尔碰撞检测(从 {len(ps)*(len(ps)-1)//2} 对中初筛出 {len(pairs)} 组潜在包络相交对)...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
|
||||
done_pair = 0
|
||||
pair_lock = threading.Lock()
|
||||
|
||||
def check_pair(pair):
|
||||
nonlocal done_pair
|
||||
a, b = pair
|
||||
t0 = time.time()
|
||||
v = common_vol(a["shape"], b["shape"])
|
||||
with pair_lock:
|
||||
done_pair += 1
|
||||
status = f"干涉 {v:.1f} mm³ ❌" if v > 2.0 else "零干涉 (0.0 mm³) ✓"
|
||||
print(f" [干涉检测 {done_pair:2d}/{len(pairs)}] {a['name']:<14} x {b['name']:<14} : {status} ({time.time()-t0:.3f}s)", flush=True)
|
||||
if v > 2.0:
|
||||
try:
|
||||
c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped)
|
||||
@@ -670,14 +729,19 @@ def collide():
|
||||
return (a["name"], b["name"], round(v, 1), loc)
|
||||
return None
|
||||
|
||||
workers = min(8, os.cpu_count() or 4)
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
results = list(ex.map(check_pair, pairs))
|
||||
rows = []
|
||||
for pair in pairs:
|
||||
res = check_pair(pair)
|
||||
if res is not None:
|
||||
rows.append(res)
|
||||
|
||||
rows = [r for r in results if r is not None]
|
||||
rows.sort(key=lambda r: -r[2])
|
||||
json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"),
|
||||
ensure_ascii=False)
|
||||
if len(rows) == 0:
|
||||
print(f"\n-> 静态碰撞干涉检测通过: 0 对干涉!全部 {len(pairs)} 组接触面对零碰撞!\n", flush=True)
|
||||
else:
|
||||
print(f"\n-> 发现 {len(rows)} 对干涉!\n", flush=True)
|
||||
return rows
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,19 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from armcore import WEB, DOCS, WORKSPACE
|
||||
SRC = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
import time
|
||||
|
||||
def rebuild_viewer():
|
||||
print("=" * 80, flush=True)
|
||||
print("【第五阶段 5/5】打包单文件三维交互查看器 (机器人手臂3D查看器.html)...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
t0 = time.time()
|
||||
print(" - 读取 Three.js 离线 3D 渲染内核...", end="", flush=True)
|
||||
three = io.open(WEB + "/three_inline.js", encoding="utf-8").read()
|
||||
assert len(three) > 400000, len(three)
|
||||
print(f" 完成 ({len(three)/1024:.0f} KB) ✓", flush=True)
|
||||
|
||||
print(" - 读取几何网格与关节运动学数据...", end="", flush=True)
|
||||
data = io.open(WEB + "/mesh_arm.json", encoding="utf-8").read()
|
||||
sheets = io.open(WEB + "/sheets.jsdata", encoding="utf-8").read()
|
||||
col = io.open(DOCS + "/collision.json", encoding="utf-8").read() if os.path.exists(
|
||||
@@ -23,6 +33,9 @@ def rebuild_viewer():
|
||||
trv = io.open(DOCS + "/travel.json", encoding="utf-8").read() if os.path.exists(
|
||||
DOCS + "/travel.json") else "[]"
|
||||
tpl = io.open(SRC + "/viewer_tpl.html", encoding="utf-8").read()
|
||||
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
|
||||
|
||||
print(" - 打包生成全内置单文件 HTML...", end="", flush=True)
|
||||
out = (tpl.replace("/*__THREE__*/", three)
|
||||
.replace("/*__DATA__*/", data)
|
||||
.replace("/*__SHEETS__*/", sheets)
|
||||
@@ -33,8 +46,9 @@ def rebuild_viewer():
|
||||
io.open(dst, "w", encoding="utf-8").write(out)
|
||||
io.open(WEB + "/index.html", "w", encoding="utf-8").write(out)
|
||||
io.open(WORKSPACE + "/机器人手臂3D查看器.html", "w", encoding="utf-8").write(out)
|
||||
print("viewer %s (and in %s) %.2f MB (three %.0f KB + data %.0f KB + sheets %.0f KB)" % (
|
||||
dst, WORKSPACE, os.path.getsize(dst) / 1e6, len(three) / 1024, len(data) / 1024, len(sheets) / 1024))
|
||||
sz_mb = os.path.getsize(dst) / 1e6
|
||||
print(f" 完成 ({sz_mb:.2f} MB, 耗时: {time.time()-t0:.2f}s) ✓", flush=True)
|
||||
print(f"-> 3D查看器已更新至: {dst} 与 根目录/机器人手臂3D查看器.html\n", flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
rebuild_viewer()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import subprocess
|
||||
import glob
|
||||
|
||||
CAND = [r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe"]
|
||||
EDGE = next((c for c in CAND if os.path.exists(c)), None)
|
||||
if not EDGE:
|
||||
EDGE = glob.glob(r"C:\Users\*\AppData\Local\Microsoft\Edge\*\msedge.exe")[0]
|
||||
|
||||
HTML = r"G:/Robot_v2/机器人手臂/web/机器人手臂3D查看器.html"
|
||||
ART = r"C:/Users/cbb/.gemini/antigravity/brain/5b3fdbf8-960a-428d-b114-f1ec3e9a8af3"
|
||||
|
||||
shots = [
|
||||
("shot_p07_az0", "?az=0&po=1.5708&rd=220&tx=18.5&ty=250&tz=-140&xray=0"),
|
||||
("shot_p07_az90", "?az=1.5708&po=1.5708&rd=220&tx=18.5&ty=250&tz=-140&xray=0"),
|
||||
("shot_p07_az180", "?az=3.14159&po=1.5708&rd=220&tx=18.5&ty=250&tz=-140&xray=0"),
|
||||
("shot_p07_azneg90", "?az=-1.5708&po=1.5708&rd=220&tx=18.5&ty=250&tz=-140&xray=0"),
|
||||
]
|
||||
|
||||
for nm, q in shots:
|
||||
out = os.path.join(ART, f"{nm}.png")
|
||||
target = f"file:///{HTML}{q}&still=1"
|
||||
cmd = [EDGE, "--headless=new", "--use-gl=angle", "--use-angle=d3d11",
|
||||
"--disable-cache", "--disable-application-cache", "--disk-cache-size=0",
|
||||
"--enable-gpu-rasterization", "--ignore-gpu-blocklist",
|
||||
"--hide-scrollbars", "--force-device-scale-factor=1",
|
||||
"--window-size=1200,900", "--virtual-time-budget=5000",
|
||||
f"--screenshot={out}", target]
|
||||
subprocess.run(cmd, capture_output=True, timeout=30)
|
||||
print(f"{nm}: size={os.path.getsize(out) if os.path.exists(out) else 0}")
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""零位静态干涉 + J4/J5 小角度细扫诊断(带干涉质心)"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import sweep_v2 as SV
|
||||
|
||||
WORKERS = 12
|
||||
|
||||
|
||||
def sweep_fine(ctx, joint_idx, name, angles, workers):
|
||||
n = len(angles)
|
||||
for s in range(0, n, 8):
|
||||
batch = angles[s:s + 8]
|
||||
results = SV.eval_angles([{joint_idx: a} for a in batch], workers)
|
||||
print(" %s 细扫 %d/%d(%.0f%%)" % (name, min(s + 8, n), n, 100.0 * min(s + 8, n) / n), flush=True)
|
||||
for a, viol in zip(batch, results):
|
||||
if viol:
|
||||
print(" %s=%+d°: %s" % (name, a, "; ".join(
|
||||
"%sx%s vol=%.0f @%s" % (p, q, v, c) for p, q, v, c in viol)), flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
ctx = SV.make_ctx()
|
||||
print("模型已构建,开始检查", flush=True)
|
||||
|
||||
print("== 零位全对全静态检查 ==", flush=True)
|
||||
v0 = SV.test_posture(ctx, {})
|
||||
if not v0:
|
||||
print(" 零位 0 对干涉")
|
||||
else:
|
||||
for na, nb, v, c in v0:
|
||||
print(" %s x %s vol=%.1f mm3 center=%s" % (na, nb, v, c))
|
||||
|
||||
angles = [a for s in (1, -1) for a in (s * i for i in range(1, 21))]
|
||||
|
||||
print("\n== J5 细扫(±40°,2° 步长,其余零位)==", flush=True)
|
||||
sweep_fine(ctx, 4, "J5", angles, WORKERS)
|
||||
|
||||
print("\n== J4 细扫(±40°,2° 步长,其余零位)==", flush=True)
|
||||
sweep_fine(ctx, 5, "J4", angles, WORKERS)
|
||||
|
||||
print("\n== J5 × J4 组合抽样 ==", flush=True)
|
||||
combos = [(j5, j4) for j5 in (-40, -20, 20, 40) for j4 in (-40, -20, 20, 40)]
|
||||
results = SV.eval_angles([{4: j4, 5: j5} for j5, j4 in combos], WORKERS)
|
||||
for (j5, j4), viol in zip(combos, results):
|
||||
if viol:
|
||||
print(" J5=%+d J4=%+d: %s" % (j5, j4, "; ".join("%sx%s vol=%.0f @%s" % (p, q, v, c) for p, q, v, c in viol)))
|
||||
print("done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,942 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Humanoid arm: 6 x the 60 mm gearmotor module on a torso mast.
|
||||
|
||||
J1 肩抬举 roll +X fore-aft, in the shoulder pod -> drives P04 肩叉
|
||||
J2 肩俯仰 pitch -Y outboard in the fork -> drives P05 大臂
|
||||
J3 肘俯仰 pitch +Y inboard in the humerus -> drives P06 小臂
|
||||
J4 前臂旋转 roll -Z coaxial inside the forearm -> drives P07 腕帽
|
||||
J5 腕俯仰 pitch -Y outboard in the cap -> drives P08 手掌
|
||||
J6 手部 -Z down the hand, crank + jaws -> drives P12 曲柄
|
||||
|
||||
Stations all come from human_layout.py, which derives them from the one hard fact about
|
||||
this joint recipe: a module is a 60 mm drum lying ALONG its own axis for 65 mm before the
|
||||
plate it drives. Consecutive pitch motors alternate inboard / outboard and share the one
|
||||
hub plane Y_H, which keeps the limb a single tube wide.
|
||||
|
||||
Four rules the earlier passes broke, now enforced in code:
|
||||
* no vertical yaw column - the arm has to hang through it. J1 is a shoulder roll about
|
||||
a fore-aft axis 92 mm above the pitch axis, and the pod keeps its cheeks at
|
||||
|x| >= 64 while the limb lives in the plane X_ARM with a +-42 envelope.
|
||||
* a motor boss ends at COVER_Z + 0.2, because the split clamp band (local 56.6..64.4,
|
||||
out to R38) belongs to the neighbouring link and needs that space free.
|
||||
* a driven hub disc stops short of the next joint's clamp envelope instead of running
|
||||
through it.
|
||||
* every bolted pair shares a plane whose normal IS the bolt axis. The hand therefore
|
||||
presents two faces: the J5 hub disc (normal Y, spline-driven) and a neck plate at the
|
||||
shell flange (normal Z, 3x M3). That 90 deg turn is what makes the hand a structure
|
||||
instead of an orange disc floating in front of the arm.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", line_buffering=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import cadquery as cq
|
||||
import arm_build as AB
|
||||
from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS)
|
||||
from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector,
|
||||
FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK,
|
||||
BORE_R, CB_R, PCB_W, D2R, T, R, PARTS)
|
||||
import human_layout as HL
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||
from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1,
|
||||
Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6,
|
||||
Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1,
|
||||
Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT,
|
||||
Y_M6, Y_CHEST, X_CHEST)
|
||||
|
||||
VERSION = os.environ.get("ARMVER", "h1")
|
||||
ITER = int(os.environ.get("ARMITER", "1"))
|
||||
AB.VERSION = VERSION
|
||||
AB.ITER = ITER
|
||||
|
||||
Y_MID = Y_AXIS # arm lateral centre plane
|
||||
BOSS = COVER_Z + 0.2 # a motor boss stops here; the clamp lives beyond
|
||||
DISC_R = 40.0 # driven hub disc (shroud OD is 38)
|
||||
SLEEVE_R = 35.0 # motor mount collar outer (OD 70, wall 4.85mm over Ø60.3 bore)
|
||||
|
||||
|
||||
def Xcyl(r, x0, x1, py, pz):
|
||||
"""Cylinder of radius r along +X from x0..x1, axis at (y,z) = (py,pz)."""
|
||||
return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz))
|
||||
|
||||
|
||||
def Xplate(pts_yz, x0, x1):
|
||||
"""Plate bounded by a (y,z) profile, extruded along x0..x1."""
|
||||
prof = [(-p[1], p[0]) for p in pts_yz]
|
||||
return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0))
|
||||
|
||||
|
||||
def XB(a0, a1, y0, y1, z0, z1):
|
||||
"""Box in the limb frame: x measured from X_ARM."""
|
||||
return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1)
|
||||
|
||||
|
||||
def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0):
|
||||
"""Direct motor output interface for joint j:
|
||||
- Completely eliminates separate couplings & clamps across the arm.
|
||||
- Fits directly to motor rotating output flange at local z = 54.942.
|
||||
- Outer disc starts at local z = 56.5 (0.5mm clearance from stationary motor cover at 56.0).
|
||||
- Centering pilot boss Ø35.6mm (R17.8mm) spans local z: 54.942 to 56.5 into motor neck.
|
||||
- Central through-hole Ø7.0mm (R3.5mm) penetrates all the way through for wire passing.
|
||||
- 3 M2 counterbored screw holes at BCD 29.0mm (angles 8.6°, 127.7°, 247.9°):
|
||||
Through hole Ø2.8mm (local z: 54.0 to 58.0, leaves 3.06mm solid clamping flange).
|
||||
Counterbore Ø5.6mm (local z: 58.0 to z_out + 30.0) penetrates ALL THE WAY THROUGH to the outside!
|
||||
"""
|
||||
disc = cyl(r_disc, 56.5, z_out)
|
||||
boss = cyl(17.8, 54.942, 56.5)
|
||||
shape = shape.fuse(j.L(disc)).fuse(j.L(boss))
|
||||
shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, z_out + 40.0, u, v)
|
||||
shape = shape.cut(j.L(th)).cut(j.L(cb))
|
||||
return shape
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- torso -------------------
|
||||
def p_chest():
|
||||
"""P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower):
|
||||
针对 3D 打印与高抗扭刚度彻底重构:
|
||||
1. 66x66mm 封闭中空厚壁箱型柱(壁厚 8mm),中心在 (-27.0, 47.0),与 P03 底座完美贴合无阶梯、无突起;
|
||||
2. 顶部接收 P03 48x48x15mm 榫头插槽与 4x M5 安装螺栓;
|
||||
3. 底部插入 P02 沉槽(深度 12mm),剪切力由槽壁全周承载;
|
||||
4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm);
|
||||
5. 外棱角全部采用 R=6mm 圆角。
|
||||
"""
|
||||
z_bot = 22.0 # P02 底座厚度 22mm
|
||||
z_top = Z_CHEST1 # 470.0
|
||||
|
||||
# 外部实心箱梁
|
||||
col = (cq.Workplane("XY", origin=(-27.0, 47.0, z_bot))
|
||||
.rect(66.0, 66.0)
|
||||
.extrude(z_top - z_bot)
|
||||
.edges("|Z").fillet(6.0)).val()
|
||||
s = cq.Shape(col.wrapped)
|
||||
|
||||
# 中空中部腔体 (50x50mm,留 8mm 强韧厚壁)
|
||||
cav = (cq.Workplane("XY", origin=(-27.0, 47.0, z_bot + 10.0))
|
||||
.rect(50.0, 50.0)
|
||||
.extrude(z_top - z_bot + 1.0)
|
||||
.edges("|Z").fillet(4.0)).val()
|
||||
s = s.cut(cav)
|
||||
|
||||
# 底部 4x M5 安装孔
|
||||
for xx in (-48.0, -6.0):
|
||||
for yy in (26.0, 68.0):
|
||||
s = s.cut(cyl(2.75, z_bot - 0.01, z_bot + 15.0, xx, yy))
|
||||
|
||||
# 顶部 4x M5 安装孔
|
||||
for xx in (-48.0, -6.0):
|
||||
for yy in (26.0, 68.0):
|
||||
s = s.cut(cyl(2.75, z_top - 20.0, z_top + 0.01, xx, yy))
|
||||
|
||||
# 走线工艺窗口(带大圆角)
|
||||
w_bot = (cq.Workplane("XZ", origin=(-27.0, 80.5, 105.0))
|
||||
.rect(34.0, 70.0)
|
||||
.extrude(-20.0)).val()
|
||||
s = s.cut(w_bot)
|
||||
|
||||
w_top = (cq.Workplane("XZ", origin=(-27.0, 80.5, 375.0))
|
||||
.rect(34.0, 70.0)
|
||||
.extrude(-20.0)).val()
|
||||
s = s.cut(w_top)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def split_p01_stack(p01):
|
||||
"""将 P01 拆分为适合 250mm 桌面 3D 打印机的两段(P01_A 与 P01_B),带 16mm 阶梯互锁榫卯"""
|
||||
z_split = 246.0
|
||||
h_lap = 16.0
|
||||
|
||||
# 下段 P01_A (Z=22 to 246, 包含向上的 16mm 榫头插舌)
|
||||
cut_b = bx(-70.0, 20.0, 0.0, 100.0, z_split, 500.0)
|
||||
tongue_outer = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split))
|
||||
.rect(64.8, 64.8)
|
||||
.extrude(h_lap)
|
||||
.edges("|Z").fillet(5.5)).val()
|
||||
tongue_cav = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split - 1.0))
|
||||
.rect(51.0, 51.0)
|
||||
.extrude(h_lap + 2.0)).val()
|
||||
tongue = cq.Shape(tongue_outer.wrapped).cut(tongue_cav)
|
||||
|
||||
p01_a = p01.cut(cut_b).fuse(tongue)
|
||||
|
||||
# 上段 P01_B (Z=246 to 470, 底部包含接收榫头的凹槽)
|
||||
cut_a = bx(-70.0, 20.0, 0.0, 100.0, 0.0, z_split)
|
||||
p01_b = p01.cut(cut_a)
|
||||
pocket = (cq.Workplane("XY", origin=(-27.0, 47.0, z_split - 0.1))
|
||||
.rect(65.6, 65.6)
|
||||
.extrude(h_lap + 0.5)
|
||||
.edges("|Z").fillet(6.0)).val()
|
||||
p01_b = p01_b.cut(pocket)
|
||||
|
||||
# 对锁螺栓孔 (4x M4 对锁孔在 Z=z_split + h_lap/2)
|
||||
z_bolt = z_split + h_lap / 2.0
|
||||
for xx in (-46.0, -8.0):
|
||||
y_bolt = Ycyl(2.2, 10.0, 85.0, xx, z_bolt)
|
||||
p01_a = p01_a.cut(y_bolt)
|
||||
p01_b = p01_b.cut(y_bolt)
|
||||
|
||||
return p01_a, p01_b
|
||||
|
||||
|
||||
def p_base_foot():
|
||||
"""P02_测试台架底座 (Ultra-Stable 320mm Extended Stabilizer Base):
|
||||
1. 200x320x22mm 重型防倾翻大底盘(长 320mm,向后延伸至 Y=-90,向前延伸至 Y=+230);
|
||||
2. 彻底保证机械臂在最大延伸(前伸 300mm+)及动态制动时的重心稳定度;
|
||||
3. 中央设计 12mm 深度立柱抗剪嵌套凹槽 (Socket Pocket),精密容纳 P01 下端;
|
||||
4. 四个边角各设有一组 28x7mm 的 M6 夹持/桌面压板安装腰槽;
|
||||
5. 凹槽底面设有 4x M5 沉头紧固螺栓孔;
|
||||
6. 底面设有 28x6mm 隐蔽过线通槽;
|
||||
7. 四周棱角全部采用 R=16mm 大圆角过渡与 2.5mm 上边缘导角。
|
||||
"""
|
||||
z0 = 0.0
|
||||
z1 = 22.0
|
||||
|
||||
base_plate = (cq.Workplane("XY", origin=(-20.0, 70.0, z0))
|
||||
.rect(200.0, 320.0)
|
||||
.extrude(z1)
|
||||
.edges("|Z").fillet(16.0)
|
||||
.edges(">Z").fillet(2.5)).val()
|
||||
s = cq.Shape(base_plate.wrapped)
|
||||
|
||||
# 中央立柱嵌装定位槽 (深度 12mm, Z: 10~22mm)
|
||||
socket_box = (cq.Workplane("XY", origin=(-27.0, 47.0, z1 - 12.0))
|
||||
.rect(67.0, 67.0)
|
||||
.extrude(13.0)
|
||||
.edges("|Z").fillet(6.5)).val()
|
||||
s = s.cut(socket_box)
|
||||
|
||||
# 底部 4x M5 贯通螺栓孔及沉头沉孔 (从底座下方向上紧固立柱)
|
||||
for xx in (-48.0, -6.0):
|
||||
for yy in (26.0, 68.0):
|
||||
th = cyl(2.75, -0.01, z1 - 11.0, xx, yy)
|
||||
cb = cyl(5.0, -0.01, 6.0, xx, yy)
|
||||
s = s.cut(th).cut(cb)
|
||||
|
||||
# 4 处边角 M6 桌面压板/台钳固定长腰槽 (28mm x 7mm)
|
||||
for cx in (-100.0, 60.0):
|
||||
for cy in (-60.0, 200.0):
|
||||
plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1))
|
||||
slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val()
|
||||
s = s.cut(slot)
|
||||
|
||||
# 底部隐蔽走线凹槽 (宽 28mm, 深 6mm, 从立柱槽直通底座后侧 Y=-90)
|
||||
wire_ch = (cq.Workplane("XY", origin=(-27.0, -20.0, -0.01))
|
||||
.rect(28.0, 145.0)
|
||||
.extrude(6.0)).val()
|
||||
s = s.cut(wire_ch)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def p_girdle(j1):
|
||||
"""P03_肩部基座 浑然一体高刚度规整风道机壳 (Monolithic Aerodynamic Shoulder Housing):
|
||||
彻底重构并消除旧版所有缺陷:
|
||||
1. 彻底消除不对称悬空凸台与多余阶梯(消除 X>5.0 处突出的无用耳朵与螺栓孔);
|
||||
2. 全件采用统一的 X 跨度 X in [-59.0, 5.0] (宽 64mm),外侧平齐,零多余外凸;
|
||||
3. 斜挑支撑柱 (Pylon) 自 Z=470mm 安装基底浑然一体平滑延伸至 Ø73mm 电机外壳舱,无上下断层或拼接触痕;
|
||||
4. 底面设置 48x48x15mm 高强度整体榫头与 4x M5 对锁安装沉孔,隐藏于腔体内部;
|
||||
5. 7 组轴向圆角散热通风百叶窗 (长34mm, 宽4.6mm) + 尾部双排气对流窗口。
|
||||
"""
|
||||
R_POD = 36.5
|
||||
X_REAR = Z_R1 - 8.0 # -59.0 (8mm 后端安装板)
|
||||
X_FRONT = 5.0 # +5.0 (平齐电机输出端面,留 0.5mm 间隙至 P04)
|
||||
|
||||
# 1. 规整同轴电机外壳舱 (Coaxial Nacelle, Ø73mm)
|
||||
pod_outer = (cq.Workplane("YZ", origin=(X_REAR, Y_AXIS, Z_RL))
|
||||
.circle(R_POD)
|
||||
.extrude(X_FRONT - X_REAR)
|
||||
.edges(">X").chamfer(2.0)
|
||||
.edges("<X").fillet(3.0)).val()
|
||||
s = cq.Shape(pod_outer.wrapped)
|
||||
|
||||
# 2. 一体化斜挑支撑柱 (Monolithic Pylon, 从 Z=470 直通电机机舱)
|
||||
pylon_pts = [
|
||||
(16.0, Z_CHEST1),
|
||||
(78.0, Z_CHEST1),
|
||||
(78.0, 542.0),
|
||||
(Y_AXIS - 5.0, Z_RL - R_POD + 4.0),
|
||||
(Y_AXIS, Z_RL - R_POD),
|
||||
(Y_AXIS - R_POD, Z_RL + 10.0),
|
||||
(Y_AXIS - R_POD - 6.0, Z_RL + 6.0),
|
||||
(70.0, 542.0),
|
||||
(16.0, Z_CHEST1 + 8.0),
|
||||
]
|
||||
pylon = Xplate(pylon_pts, X_REAR, X_FRONT)
|
||||
s = s.fuse(pylon)
|
||||
|
||||
# 3. 底部与立柱对接的精密定位榫头 (Z=455~470mm)
|
||||
boss = (cq.Workplane("XY", origin=(-27.0, 47.0, Z_CHEST1 - 15.0))
|
||||
.rect(48.0, 48.0)
|
||||
.extrude(15.0)
|
||||
.edges("|Z").fillet(4.0)).val()
|
||||
s = s.fuse(cq.Shape(boss.wrapped))
|
||||
|
||||
# 4. 4x M5 坚固连接螺孔 (隐蔽分布于基底,不露在外部侧面)
|
||||
for xx in (-48.0, -6.0):
|
||||
for yy in (26.0, 68.0):
|
||||
s = s.cut(cyl(2.75, Z_CHEST1 - 16.0, Z_CHEST1 + 25.0, xx, yy))
|
||||
|
||||
# 5. 电机安装接口与内腔
|
||||
s = j1.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j1.front_collar_pocket(s, r_bore=30.25 + 0.5, z0=50.0, z1=56.5)
|
||||
s = j1.pocket(s)
|
||||
s = s.cut(Xcyl(30.6, Z_R1 + 0.5, -0.5, Y_AXIS, Z_RL))
|
||||
|
||||
# 6. 7组轴向散热百叶窗 (Axial Louvers)
|
||||
slot_angles = [-15, 12, 38, 64, 90, 116, 142]
|
||||
slot_w = 4.6
|
||||
slot_l = 34.0
|
||||
slot_x_mid = -24.0
|
||||
for ang in slot_angles:
|
||||
rad = ang * D2R
|
||||
plane = cq.Plane(
|
||||
origin=(slot_x_mid, Y_AXIS + 28.0 * math.cos(rad), Z_RL + 28.0 * math.sin(rad)),
|
||||
xDir=(1, 0, 0),
|
||||
normal=(0, math.cos(rad), math.sin(rad))
|
||||
)
|
||||
slot_solid = cq.Workplane(plane).slot2D(slot_l, slot_w).extrude(16.0).val()
|
||||
s = s.cut(slot_solid)
|
||||
|
||||
# 7. 尾部 PCBA 驱动板散热窗口 (PCBA Vents)
|
||||
for z_off in (-10.0, 10.0):
|
||||
vent_plane = cq.Plane(
|
||||
origin=(X_REAR - 1.0, Y_AXIS, Z_RL + z_off),
|
||||
xDir=(0, 1, 0),
|
||||
normal=(1, 0, 0)
|
||||
)
|
||||
vent_solid = cq.Workplane(vent_plane).slot2D(16.0, 5.0).extrude(12.0).val()
|
||||
s = s.cut(vent_solid)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def p_fork(j1, j2):
|
||||
"""P04_肩叉 (Shoulder Fork / L-Bracket):
|
||||
Driven by J1 roll output, drops directly to hold M2 (shoulder pitch motor).
|
||||
Aligend along Y_AXIS = 128.0 mm (both M1 and M2 centered at Y=128!).
|
||||
Fixes M2 strictly via two ends (4x rear bolts @ BCD 53.7mm + front collar).
|
||||
Stator middle cylinder is 100% exposed for cooling. ZERO pinch screws!
|
||||
严格依照用户绘制红线进行 XZ 双曲面有机流线塑形:
|
||||
1. 右侧由 J1 凸台下方向内收腰 (x=35.0, z=550.0),底端以 R=106.5mm 大直径切线圆弧顺滑外扩,
|
||||
以严格数学相切连续顺接下方 M2 圆柱最右侧极值点 (x=53.5, z=490.0),彻底消除原先 13mm 生硬台阶;
|
||||
2. 左侧从 J1 从动盘下端 (x=5.5, z=572.0) 顺滑斜向下顺接至立柱 (x=14.0, z=515.0),填实原先悬空凹槽并大幅强化连接刚度;
|
||||
3. 采用高精度双向曲面求交 (BRepAlgoAPI_Common) 构建单实体高刚度流线型结构。
|
||||
"""
|
||||
x0 = X_J1_HUB # 14.0
|
||||
|
||||
# 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm)
|
||||
rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH)
|
||||
|
||||
# 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - 恢复并加固蓝色的环!
|
||||
front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH)
|
||||
s = rear_plate.fuse(front_collar)
|
||||
|
||||
# 3. J1 从动法兰盘 (X: 5.5..23.0, R=38mm)
|
||||
disc_j1 = Xcyl(38.0, 5.5, 23.0, Y_AXIS, Z_RL)
|
||||
s = s.fuse(disc_j1)
|
||||
|
||||
# 4. XZ 平面有机连续流线轮廓(加固并彻底抹平顶部 26mm 生硬阶梯):
|
||||
# 顶部:自 (5.5, 648.0) 保持与 J1 从动盘同高延伸至 (23.0, 648.0),完全抹平阶梯!
|
||||
# 右侧:自 (23.0, 648.0) 以 R=18mm 顺滑倒角过渡到 (39.0, 630.0),再以 R=111.56mm 切线大圆弧顺接下方 (53.5, 490.0)
|
||||
# 左侧:维持 Hermite C1 切线连续自然过渡
|
||||
dx_flare = 53.5 - 39.0
|
||||
dz_flare = 545.0 - 490.0
|
||||
R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) # 111.56 mm
|
||||
|
||||
x_b, z_b = -12.65, 506.0
|
||||
x_a, z_a = 5.5, 545.0
|
||||
Lz = z_a - z_b
|
||||
d0 = 0.514 * Lz
|
||||
d1 = 0.0
|
||||
|
||||
hermite_pts = []
|
||||
N_h = 16
|
||||
for i in range(N_h + 1):
|
||||
t = i / N_h
|
||||
h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0
|
||||
h10 = t**3 - 2.0 * (t**2) + t
|
||||
h01 = -2.0 * (t**3) + 3.0 * (t**2)
|
||||
h11 = t**3 - t**2
|
||||
xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11
|
||||
zh = z_b + t * Lz
|
||||
hermite_pts.append((xh, zh))
|
||||
|
||||
xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
||||
.moveTo(5.5, 545.0)
|
||||
.lineTo(5.5, Z_RL + 38.0) # 648.0: 与 J1 圆盘顶部平齐共顶!
|
||||
.lineTo(23.0, Z_RL + 38.0) # 抹平 X=23 处的 26mm 巨型台阶!
|
||||
.radiusArc((39.0, 630.0), 18.0) # 自然圆角顺滑过渡到外侧肩部
|
||||
.lineTo(39.0, 545.0)
|
||||
.radiusArc((53.5, 490.0), -R_flare)
|
||||
.lineTo(18.5, 490.0)
|
||||
.lineTo(-16.5, 490.0)
|
||||
.threePointArc((-15.1, 498.5), (-12.65, 506.0)))
|
||||
for pt in hermite_pts[1:]:
|
||||
xz_wire = xz_wire.lineTo(pt[0], pt[1])
|
||||
xz_wire = xz_wire.close()
|
||||
xz_solid = xz_wire.extrude(100.0, both=True).val()
|
||||
xz_shape = cq.Shape(xz_solid.wrapped)
|
||||
|
||||
# YZ 平面包络:
|
||||
# 顶部顺接 J1 圆盘 (Y_AXIS - 38 .. Y_AXIS + 38, Z=648)
|
||||
# 底端全深顺接 M2 前后安装端 (Y: 92.0 .. 155.5)
|
||||
yz_pts = [
|
||||
(Y_AXIS - 38.0, Z_RL + 38.0),
|
||||
(Y_AXIS + 38.0, Z_RL + 38.0),
|
||||
(Y_COVER - 0.5, Z_SH + 36.0),
|
||||
(Y_COVER - 0.5, Z_SH),
|
||||
(Y_REAR - 8.0, Z_SH),
|
||||
(Y_REAR - 8.0, Z_SH + 36.0),
|
||||
]
|
||||
yz_solid = Xplate(yz_pts, -20.0, 60.0)
|
||||
|
||||
c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped)
|
||||
c_int.Build()
|
||||
column = cq.Shape(c_int.Shape())
|
||||
|
||||
s = s.fuse(column)
|
||||
|
||||
|
||||
# 6. 中部电机定子散热切除(R=30.5,保证电机定子散热,同时保留外侧与上下坚固承力壁)
|
||||
s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH))
|
||||
|
||||
# 7. M2 接口成型
|
||||
s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
|
||||
s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
c_m2 = [cyl(BORE_R, -0.6, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6)]
|
||||
s = s.cut(*[j2.L(item) for item in c_m2])
|
||||
|
||||
|
||||
|
||||
# 7. J1 直连法兰结构(定位止口 + 走线通孔 + 3x M2 贯通沉头孔)
|
||||
boss = cyl(17.8, 54.942, 56.5)
|
||||
s = s.fuse(j1.L(boss))
|
||||
s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
|
||||
s = s.cut(j1.L(th)).cut(j1.L(cb))
|
||||
|
||||
return s
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- limbs -------------------
|
||||
def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket"):
|
||||
"""Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08):
|
||||
1. 顶部从动法兰与前侧 Loft 自然相切顺接;
|
||||
2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉;
|
||||
3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合;
|
||||
4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。
|
||||
"""
|
||||
z_bot = z_r - 56.0 # roll 电机底端 (front collar)
|
||||
z_plate = z_r + 7.0 # roll 电机顶端安装板 (rear plate top)
|
||||
y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面)
|
||||
y_hub_in = HL.Y_COVER + 1.0 # 157.0 (从动法兰内侧面,预留 1.0mm 动间隙)
|
||||
y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值)
|
||||
|
||||
# 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm)
|
||||
s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS)
|
||||
|
||||
# 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm)
|
||||
disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch)
|
||||
s = s.fuse(disc)
|
||||
|
||||
# 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph)
|
||||
Z_top = z_pitch - 35.0
|
||||
z_start = z_bot
|
||||
N = 16
|
||||
wp = cq.Workplane("XY")
|
||||
for i in range(N + 1):
|
||||
t = i / float(N)
|
||||
z_curr = z_start + t * (Z_top - z_start)
|
||||
st = 3.0 * (t**2) - 2.0 * (t**3)
|
||||
y_center = y_cyl_max + st * (y_out - y_cyl_max)
|
||||
y_side = HL.Y_AXIS + st * (y_out - HL.Y_AXIS)
|
||||
v_c = y_center - HL.Y_AXIS
|
||||
v_s = y_side - HL.Y_AXIS
|
||||
v_mid = max(v_c, v_s + 0.02)
|
||||
w = (cq.Workplane("XY", origin=(HL.X_ARM, HL.Y_AXIS, z_curr))
|
||||
.moveTo(-35.0, v_s)
|
||||
.threePointArc((0.0, v_mid), (35.0, v_s))
|
||||
.lineTo(35.0, -5.0)
|
||||
.lineTo(-35.0, -5.0)
|
||||
.close())
|
||||
wp = wp.add(w)
|
||||
|
||||
loft = wp.toPending().loft()
|
||||
s = s.fuse(loft.val())
|
||||
|
||||
# 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域)
|
||||
top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0))
|
||||
.moveTo(y_hub_in, Z_top)
|
||||
.lineTo(y_out, Z_top)
|
||||
.lineTo(y_out, z_pitch)
|
||||
.lineTo(y_hub_in, z_pitch)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(top_fill.val())
|
||||
|
||||
# 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料,
|
||||
# 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整!
|
||||
cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0))
|
||||
.moveTo(-999.0, z_plate)
|
||||
.lineTo(y_hub_in, z_plate)
|
||||
.lineTo(y_hub_in, z_pitch + 50.0)
|
||||
.lineTo(-999.0, z_pitch + 50.0)
|
||||
.close()
|
||||
.extrude(60.0, both=True))
|
||||
s = s.cut(cut_top_solid.val())
|
||||
|
||||
# 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强
|
||||
cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0)
|
||||
s = s.cut(cut_open)
|
||||
|
||||
# 7. 上级电机同心圆弧避让
|
||||
clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch)
|
||||
s = s.cut(clearance)
|
||||
|
||||
# 8. 下方电机安装孔位与定位止口
|
||||
s = j_roll.rear_mount_holes(s, t_plate=7.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
|
||||
s = j_roll.pocket(s)
|
||||
|
||||
# 9. 电机定子配合空腔
|
||||
s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS))
|
||||
|
||||
# 10. 俯仰轴直连法兰结构(凸台延伸至 59.5 跨过间隙与法兰盘牢固熔接,保持单实体)
|
||||
boss = cyl(17.8, 54.942, 59.5)
|
||||
s = s.fuse(j_pitch.L(boss))
|
||||
s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
|
||||
s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb))
|
||||
|
||||
# 11. 竖直臂前侧外角倒角(5mm):平滑消除圆柱体以外的两侧垂直尖角
|
||||
c_size = 5.0
|
||||
c_left = (cq.Workplane("XY", origin=(HL.X_ARM - 35.0, y_hub_in, z_plate))
|
||||
.moveTo(-0.1, -0.1)
|
||||
.lineTo(c_size, -0.1)
|
||||
.lineTo(-0.1, c_size)
|
||||
.close()
|
||||
.extrude(z_pitch - z_plate + 10.0))
|
||||
c_right = (cq.Workplane("XY", origin=(HL.X_ARM + 35.0, y_hub_in, z_plate))
|
||||
.moveTo(0.1, -0.1)
|
||||
.lineTo(-c_size, -0.1)
|
||||
.lineTo(0.1, c_size)
|
||||
.close()
|
||||
.extrude(z_pitch - z_plate + 10.0))
|
||||
s = s.cut(c_left.val()).cut(c_right.val())
|
||||
|
||||
# 12. L 型转角连接处倒角过渡(倒角 2.0mm):
|
||||
# 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中,严控动间隙保证动态零干涉
|
||||
try:
|
||||
wp_s = cq.Workplane(obj=s)
|
||||
sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate))
|
||||
s = wp_s.edges(sel_l).chamfer(2.0).val()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def p_shoulder_roll_base(j2, j3, half=None):
|
||||
"""P05_大臂旋转座 (Shoulder Pitch-to-Roll Base)"""
|
||||
return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, name="P05_大臂旋转座")
|
||||
|
||||
|
||||
|
||||
def p_humerus_bone(j3, j4):
|
||||
"""P06_大臂骨架 (Humerus Bone):
|
||||
受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动,
|
||||
向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。
|
||||
1. XZ 视图满宽 Ø70mm 直通过渡:左右两侧(X: -16.5..53.5)全宽平滑垂直到达 Z_EL,严格相切融入 M4 下圆柱,彻底消除腰身台阶断差;
|
||||
2. YZ 视图连续相切双 S 弯有机顺接:顶端 Ø70mm 圆柱与底端前端套筒(Y=155.5)及后端安装板(Y=92.0)严格切线顺接,彻底消除水平方顶阶梯平台;
|
||||
3. 取消侧壁贯穿开孔,消除镂空,保持骨架为浑然一体的高强度流线型实体。
|
||||
"""
|
||||
# 1. M4 底部抱紧套筒与后端安装板 (R=35mm)
|
||||
rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL)
|
||||
front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL)
|
||||
s = rear_m4.fuse(front_m4)
|
||||
|
||||
# 顶端 Disc 结合面及底端过渡高度
|
||||
z_top = 388.5
|
||||
z_bot = Z_EL + 35.0 # 367.0
|
||||
|
||||
# 2. XZ 平面满宽流线轮廓(左右两侧严格相切于底端 M4 轴线圆柱,无收腰台阶):
|
||||
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
||||
.moveTo(-16.5, Z_EL)
|
||||
.lineTo(-16.5, z_top)
|
||||
.lineTo(53.5, z_top)
|
||||
.lineTo(53.5, Z_EL)
|
||||
.close())
|
||||
xz_solid = xz_w.extrude(100.0, both=True).val()
|
||||
xz_shape = cq.Shape(xz_solid.wrapped)
|
||||
|
||||
# 3. YZ 平面双向精确切线 S 弯顺接(前后平滑连续切线顺接,严控与 P07 动间隙):
|
||||
dz_f = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0 # 3.75 mm
|
||||
R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f) # 17.283 mm
|
||||
p_mid_f = (163.0 - dy_f, z_top - dz_f)
|
||||
|
||||
theta_max_f = math.asin(dz_f / R_f)
|
||||
C_f1 = (Y_COVER - 0.5 + R_f, z_bot)
|
||||
p_quarter_f1 = (C_f1[0] - R_f * math.cos(theta_max_f / 2.0),
|
||||
C_f1[1] + R_f * math.sin(theta_max_f / 2.0))
|
||||
C_f2 = (163.0 - R_f, z_top)
|
||||
p_quarter_f2 = (C_f2[0] + R_f * math.cos(theta_max_f / 2.0),
|
||||
C_f2[1] - R_f * math.sin(theta_max_f / 2.0))
|
||||
|
||||
dz_r = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0 # 0.5 mm
|
||||
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r) # 115.8125 mm
|
||||
p_mid_r = (93.0 - dy_r, z_top - dz_r)
|
||||
|
||||
theta_max_r = math.asin(dz_r / R_r)
|
||||
C_r1 = (Y_REAR - 8.0 - R_r, z_bot)
|
||||
p_quarter_r1 = (C_r1[0] + R_r * math.cos(theta_max_r / 2.0),
|
||||
C_r1[1] + R_r * math.sin(theta_max_r / 2.0))
|
||||
C_r2 = (93.0 + R_r, z_top)
|
||||
p_quarter_r2 = (C_r2[0] - R_r * math.cos(theta_max_r / 2.0),
|
||||
C_r2[1] - R_r * math.sin(theta_max_r / 2.0))
|
||||
|
||||
yz_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(Y_REAR - 8.0, Z_EL)
|
||||
.lineTo(Y_COVER - 0.5, Z_EL)
|
||||
.lineTo(Y_COVER - 0.5, z_bot)
|
||||
.threePointArc(p_quarter_f1, p_mid_f)
|
||||
.threePointArc(p_quarter_f2, (163.0, z_top))
|
||||
.lineTo(93.0, z_top)
|
||||
.threePointArc(p_quarter_r2, p_mid_r)
|
||||
.threePointArc(p_quarter_r1, (Y_REAR - 8.0, z_bot))
|
||||
.close())
|
||||
yz_solid = yz_w.extrude(50.0, both=True).val()
|
||||
yz_shape = cq.Shape(yz_solid.wrapped)
|
||||
|
||||
c_int = BRepAlgoAPI_Common(yz_shape.wrapped, xz_shape.wrapped)
|
||||
c_int.Build()
|
||||
body = cq.Shape(c_int.Shape())
|
||||
s = s.fuse(body)
|
||||
|
||||
# 4. 电机中间定子散热裸露切除 (100% 裸露)
|
||||
s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
|
||||
|
||||
# 5. 保持腰身为坚固完整实体,移除侧向镂空贯穿槽
|
||||
|
||||
# 6. M4 接口成型
|
||||
s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
|
||||
s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j4.pocket(s)
|
||||
s = make_direct_hub(s, j3, r_disc=35.0, z_out=58.0)
|
||||
return s
|
||||
|
||||
|
||||
def p_forearm_bone(j4, j5, half=None):
|
||||
"""P07_小臂骨架 L型高刚度无干涉版(深度圆角优化版)
|
||||
设计要点:
|
||||
1. 改为与 P05/P08 统一的 L 型结构(L-bracket):
|
||||
- +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5)
|
||||
- 垂直立柱与下方 M5 电机水平座之间采用平滑双相切内凹大圆弧 (R=28.0mm) 过渡(用户要求圆弧过渡取代折角)
|
||||
- 彻底消除侵入下方旋转副的悬空小方块/阶梯(用户要求"把这个小的这块都给消掉")
|
||||
2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热
|
||||
3. J4/J5 轴俯仰大范围自由运动零干涉
|
||||
4. 满足单实体 (solids=1) 与装配零碰撞。
|
||||
"""
|
||||
CLEARANCE_GAP = 2.0
|
||||
y_out = Y_HUB + PLATE_T # 174.0
|
||||
y_hub_in = HL.Y_COVER + 0.5 + CLEARANCE_GAP # 158.5
|
||||
y_front = Y_COVER - 0.5 # 155.5
|
||||
y_rear = Y_REAR - 8.0 # 92.0
|
||||
z_top_housing = Z_WR + 35.0 # 257.0
|
||||
|
||||
# 1. J5 电机前后安装环
|
||||
collar_front = Ycyl(35.0, HL.Y_COVER - 6.0, HL.Y_COVER - 0.5, X_ARM, Z_WR)
|
||||
rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR)
|
||||
s = collar_front.fuse(rear_plate)
|
||||
|
||||
# 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL)
|
||||
disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL)
|
||||
s = s.fuse(disc)
|
||||
|
||||
# 3. M5 电机壳体水平顶部顶板 (Z: Z_WR..z_top_housing, Y: y_rear..y_front)
|
||||
roof_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_rear, Z_WR)
|
||||
.lineTo(y_rear, z_top_housing)
|
||||
.lineTo(y_front, z_top_housing)
|
||||
.lineTo(y_front, Z_WR)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(roof_w.val())
|
||||
|
||||
# 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座)
|
||||
vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_hub_in, z_top_housing)
|
||||
.lineTo(y_out, z_top_housing)
|
||||
.lineTo(y_out, Z_EL)
|
||||
.lineTo(y_hub_in, Z_EL)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(vert_leg.val())
|
||||
|
||||
# 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块")
|
||||
# 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm)
|
||||
# 起点在顶板平整面 (y_arc_start, z_top_housing),精确相切至立柱内侧面 (y_hub_in, z_arc_end)
|
||||
# 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起!
|
||||
R_arc = 28.0
|
||||
y_arc_start = y_hub_in - R_arc
|
||||
z_arc_end = z_top_housing + R_arc
|
||||
p_mid_arc = (y_arc_start + R_arc * math.cos(math.pi / 4),
|
||||
z_arc_end - R_arc * math.sin(math.pi / 4))
|
||||
|
||||
gusset_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_arc_start, z_top_housing)
|
||||
.threePointArc(p_mid_arc, (y_hub_in, z_arc_end))
|
||||
.lineTo(y_out, z_arc_end)
|
||||
.lineTo(y_out, z_top_housing)
|
||||
.lineTo(y_arc_start, z_top_housing)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(gusset_w.val())
|
||||
|
||||
# 6. 下级 P08 旋转圆盘动态避让切除(避让下级 J5 旋转部件)
|
||||
p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR)
|
||||
s = s.cut(p08_clearance)
|
||||
|
||||
# 7. M5 电机安装接口与定子空腔
|
||||
s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5)
|
||||
s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j5.pocket(s)
|
||||
s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, y_rear + 0.5, y_front - 6.0, X_ARM, Z_WR))
|
||||
|
||||
# 半包散热设计:-Z底部电机定子通风口
|
||||
vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0)
|
||||
s = s.cut(vent_cut)
|
||||
|
||||
# 8. J4 直连法兰结构(凸台延伸至 60.0 跨过间隙与法兰盘牢固熔接,保持单实体)
|
||||
boss = cyl(17.8, 54.942, 60.0)
|
||||
s = s.fuse(j4.L(boss))
|
||||
s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
|
||||
s = s.cut(j4.L(th)).cut(j4.L(cb))
|
||||
|
||||
# 9. 工艺走线通孔
|
||||
slot_z = (Z_EL + z_top_housing) / 2.0
|
||||
slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z)
|
||||
s = s.cut(slot)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def p_wrist_bracket(j5, j6):
|
||||
"""P08_腕偏转座 (Wrist Pitch-to-Roll Bracket)"""
|
||||
return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, name="P08_腕偏转座")
|
||||
|
||||
|
||||
|
||||
def p_tool_flange(j6):
|
||||
"""P09_末端工具法兰 (Tool Output Flange):
|
||||
受 J6 (工具自转, -Z轴, hub@Z_HUB6=64.0) 驱动,直接集成 3x M2 直锁电机输出轴,
|
||||
彻底取消黄色独立联轴器与抱箍。
|
||||
提供工业标准法兰接口,集成 4x M4 螺栓孔(BCD 40mm)、Ø20mm 定位止口及 Ø10mm 贯通走线孔,
|
||||
可随时直接快拆安装各类微型夹爪或末端工具。
|
||||
外轮廓统一 Ø70mm,与腕部整体平顺过渡。
|
||||
"""
|
||||
z_bot = Z_HUB6 - PLATE_T - 9.0
|
||||
s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS)
|
||||
|
||||
# Standard 4x M4 bolt holes on BCD 40mm
|
||||
for a in (0, 90, 180, 270):
|
||||
rad = 20.0
|
||||
u = rad * math.cos(a * D2R)
|
||||
v = rad * math.sin(a * D2R)
|
||||
h = cyl(1.85, z_bot - 1.0, z_bot + 12.0, X_ARM + u, Y_AXIS + v)
|
||||
cb = cyl(3.5, z_bot - 1.0, z_bot + 4.0, X_ARM + u, Y_AXIS + v)
|
||||
s = s.cut(h).cut(cb)
|
||||
|
||||
# Central locating pilot hole (Ø20mm x 2.5mm deep) + wire pass hole (Ø10mm)
|
||||
pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS)
|
||||
thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS)
|
||||
s = s.cut(pilot).cut(thru)
|
||||
|
||||
# Direct motor output interface (3x M2 screws through to outside face)
|
||||
s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0)
|
||||
return s
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- assembly ----------------
|
||||
# ---------------------------------------------------------------- assembly ----------------
|
||||
def build():
|
||||
print("=" * 80, flush=True)
|
||||
print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
t_stage1 = time.time()
|
||||
|
||||
print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True)
|
||||
t0 = time.time()
|
||||
motor = cq.importers.importStep(AB.SRC_STEP).val()
|
||||
motor = place(motor, T(0, 0, -2.57))
|
||||
AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None
|
||||
print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True)
|
||||
|
||||
JS = {}
|
||||
for tag, cn, rear, d, xd, holder, driven in HL.JOINTS:
|
||||
JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0)
|
||||
j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7))
|
||||
|
||||
PARTS[:] = []
|
||||
AB.CENTS.clear()
|
||||
AB.GRP = {}
|
||||
par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"}
|
||||
driven = {"J1": "P04_肩叉", "J2": "P05_大臂旋转座", "J3": "P06_大臂骨架",
|
||||
"J4": "P07_小臂骨架", "J5": "P08_腕偏转座", "J6": "P09_工具法兰"}
|
||||
motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3",
|
||||
"J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"}
|
||||
for t in ("J1", "J2", "J3", "J4", "J5", "J6"):
|
||||
AB.GRP[motor_of[t]] = par[t] # the motor body is the proximal side
|
||||
AB.GRP[driven[t]] = t
|
||||
AB.GRP.update({"P01_胸板": "root", "P02_底脚": "root", "P03_肩吊臂": "root"})
|
||||
AB.JAW = {}
|
||||
|
||||
parts_to_build = [
|
||||
("P01_胸板", "躯干/胸腔安装板", lambda: p_chest(), 0x4b5563,
|
||||
"手臂根部;12 厚板,顶面与肩吊臂桥板用 4x M4 连接,另有走线窗"),
|
||||
("P02_底脚", "站立底脚", lambda: p_base_foot(), 0x374151,
|
||||
"0..%d,与机架安装面连接" % Z_CHEST0),
|
||||
("P03_肩吊臂", "肩部基座(对标灵犀X1,胸部直出)", lambda: p_girdle(j1), 0x4b5563,
|
||||
"胸腔顶部直出基座;抱紧M1定子"),
|
||||
("P04_肩叉", "正交肩肘(取消独立联轴器,3×M2螺钉直锁M1输出轴,贯通沉头孔)", lambda: p_fork(j1, j2), 0x2563eb,
|
||||
"受 J1 驱动,一体化 3x M2 螺钉直锁 M1 电机输出轴,贯通沉头孔;90°一体化高刚度正交转向肘,抱紧 M2 电机"),
|
||||
("P05_大臂旋转座", "大臂旋转座(取消独立联轴器,3×M2螺钉直锁M2输出轴,贯通沉头孔)", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669,
|
||||
"受 J2 驱动,彻底取消黄色独立联轴器;基座直接贴合 M2 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;90°正交抱紧 M3 电机,中间 100% 敞开散热"),
|
||||
("P06_大臂骨架", "大臂骨架(取消独立联轴器,3×M2螺钉直锁M3输出轴,贯通沉头孔)", lambda: p_humerus_bone(j3, j4), 0x0d9488,
|
||||
"受 J3 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M3 输出轴;流线型工字骨架+走线槽,底端抱紧 M4 肘部电机"),
|
||||
("P07_小臂骨架", "小臂骨架(L型圆弧加强版,R=28mm双相切平滑圆弧,消除悬空块)", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed,
|
||||
"受 J4 驱动,彻底取消黄色独立联轴器;从动盘直接贴合 M4 输出轴,3 颗 M2 贯通沉头螺钉直锁输出轴;轻量高刚度骨架,底端抱紧 M5 电机"),
|
||||
("P08_腕偏转座", "腕部转接座(取消独立联轴器,3×M2螺钉直锁M5输出轴,贯通沉头孔)", lambda: p_wrist_bracket(j5, j6), 0xd97706,
|
||||
"受 J5 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M5 输出轴;两端刚性固定 M6 立式电机,中间 100% 敞开散热"),
|
||||
("P09_工具法兰", "末端工具法兰(取消独立联轴器,3×M2螺钉直锁M6输出轴,贯通沉头孔)", lambda: p_tool_flange(j6), 0xdc2626,
|
||||
"受 J6 驱动,彻底取消黄色独立联轴器;一体化 3x M2 贯通沉头螺钉直锁 M6 输出轴;标准工业法兰接口,4x M4 螺孔 BCD 40mm + 定位止口与走线孔"),
|
||||
]
|
||||
|
||||
for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1):
|
||||
print(f" [{idx}/9] 正在构建 {name} ({cn})...", end="", flush=True)
|
||||
t0 = time.time()
|
||||
sh = fn()
|
||||
dt = time.time() - t0
|
||||
vol = AB.vol_of(sh) / 1000.0
|
||||
n_solids = len(solids_of(sh))
|
||||
print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True)
|
||||
add(name, cn, sh, color, note=note)
|
||||
|
||||
print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True)
|
||||
t0 = time.time()
|
||||
for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS):
|
||||
j = JS[tag]
|
||||
add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af,
|
||||
printed=False, note="复用 电机.STEP")
|
||||
print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True)
|
||||
print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True)
|
||||
return JS
|
||||
|
||||
|
||||
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
||||
SEP = 74.0 # mm of separation per joint in the exploded view
|
||||
|
||||
|
||||
def explode_chain(p, JS, cents, gmean):
|
||||
"""Explode as a readable stack, not along the joint axes."""
|
||||
grp = AB.GRP.get(p["name"], "root")
|
||||
if grp == "root":
|
||||
return [0.0, 0.0, 0.0]
|
||||
k = CHAIN.index(grp)
|
||||
off = [0.0, 0.0, -SEP * k]
|
||||
j = JS.get(grp)
|
||||
if j:
|
||||
nm = p["name"]
|
||||
w = 0.0
|
||||
if "半联轴器" in nm or "抱箍" in nm:
|
||||
w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0)
|
||||
elif nm.startswith("M"):
|
||||
w = 62.0
|
||||
for q in range(3):
|
||||
off[q] += j.d[q] * w
|
||||
return off
|
||||
|
||||
|
||||
def solids_first(path):
|
||||
return solids_of(cq.importers.importStep(path).val())[0].wrapped
|
||||
|
||||
|
||||
FILLET_R = float(os.environ.get("FILLET_R", "0"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
JS = build()
|
||||
if FILLET_R > 0:
|
||||
from arm_build import vol_of, bbox_str
|
||||
import filletlib as FL
|
||||
tot = 0
|
||||
for p in PARTS:
|
||||
if not p["printed"]:
|
||||
continue
|
||||
others = [(FL._bb(q["shape"]), q["shape"]) for q in PARTS
|
||||
if q is not p and q["vol"] > 1.0]
|
||||
sh, n, sk = FL.round_internal(p["shape"], FILLET_R, others)
|
||||
dv = (vol_of(sh) - p["vol"]) / 1000.0
|
||||
tot += n
|
||||
p["shape"], p["vol"], p["bb"] = sh, vol_of(sh), bbox_str(sh)
|
||||
print(" fillet %-10s %2d corners @R%.1f %+.2f cm3 (%d skipped: clearance)"
|
||||
% (p["name"], n, FILLET_R, dv, sk), flush=True)
|
||||
print(" fillet total: %d rounded corners" % tot, flush=True)
|
||||
|
||||
print("=" * 80, flush=True)
|
||||
print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
bad = 0
|
||||
printed_list = [p for p in PARTS if p["printed"]]
|
||||
for idx, p in enumerate(printed_list, 1):
|
||||
n = len(solids_of(p["shape"]))
|
||||
if n != 1:
|
||||
bad += 1
|
||||
status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌"
|
||||
print(f" [{idx}/9] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True)
|
||||
if bad == 0:
|
||||
print("-> 单实体检查全部通过!9/9 构件均为完美单一流形拓扑 ✓\n", flush=True)
|
||||
else:
|
||||
print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True)
|
||||
|
||||
AB.explode_vec = explode_chain
|
||||
bom = AB.export(JS)
|
||||
cl = AB.collide()
|
||||
|
||||
try:
|
||||
from build_viewer import rebuild_viewer
|
||||
rebuild_viewer()
|
||||
except Exception as e:
|
||||
print("rebuild_viewer error:", e, flush=True)
|
||||
|
||||
print("=" * 80, flush=True)
|
||||
print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True)
|
||||
print("=" * 80, flush=True)
|
||||
sys.stdout.flush()
|
||||
os._exit(0)
|
||||
@@ -545,39 +545,36 @@ def p_shoulder_roll_base(j2, j3, half=None):
|
||||
|
||||
|
||||
def p_humerus_bone(j3, j4):
|
||||
"""优化的大臂骨架:流线型、高刚度、圆润过渡
|
||||
关键改进:
|
||||
1. XZ轮廓采用完整大圆弧(R=400mm),消除所有折线
|
||||
2. YZ双S弯过渡半径增大,确保G1连续
|
||||
3. 增加内部加强筋,提升抗弯刚度
|
||||
4. 所有边缘增加倒角/圆角
|
||||
5. 电机间隙增加至2.0mm
|
||||
"""P06_大臂骨架 (Humerus Bone):
|
||||
受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动,
|
||||
向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。
|
||||
1. XZ 视图满宽 Ø70mm 直通过渡:左右两侧(X: -16.5..53.5)全宽平滑垂直到达 Z_EL,严格相切融入 M4 下圆柱,彻底消除腰身台阶断差;
|
||||
2. YZ 视图连续相切双 S 弯有机顺接:顶端 Ø70mm 圆柱与底端前端套筒(Y=155.5)及后端安装板(Y=92.0)严格切线顺接,彻底消除水平方顶阶梯平台;
|
||||
3. 取消侧壁贯穿开孔,消除镂空,保持骨架为浑然一体的高强度流线型实体。
|
||||
"""
|
||||
# 1. M4底部抱紧套筒
|
||||
# 1. M4 底部抱紧套筒与后端安装板 (R=35mm)
|
||||
rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL)
|
||||
front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL)
|
||||
s = rear_m4.fuse(front_m4)
|
||||
|
||||
# 顶端 Disc 结合面及底端过渡高度
|
||||
z_top = 388.5
|
||||
z_bot = Z_EL + 35.0 # 367.0
|
||||
|
||||
# 2. XZ平面优化流线轮廓:采用更大的过渡半径,完全消除台阶
|
||||
# 使用完整的圆弧过渡,而非直线段
|
||||
# 2. XZ 平面满宽流线轮廓(左右两侧严格相切于底端 M4 轴线圆柱,无收腰台阶):
|
||||
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
||||
.moveTo(-16.5, Z_EL)
|
||||
.lineTo(-16.5, z_bot)
|
||||
.threePointArc((-14.0, (z_bot + z_top) / 2.0), (-16.5, z_top))
|
||||
.lineTo(-16.5, z_top)
|
||||
.lineTo(53.5, z_top)
|
||||
.threePointArc((51.0, (z_bot + z_top) / 2.0), (53.5, z_bot))
|
||||
.lineTo(53.5, Z_EL)
|
||||
.close())
|
||||
xz_solid = xz_w.extrude(100.0, both=True).val()
|
||||
xz_shape = cq.Shape(xz_solid.wrapped)
|
||||
|
||||
# 3. YZ平面双向精确切线S弯 - 增加过渡半径
|
||||
dz_f = (z_top - z_bot) / 2.0
|
||||
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0
|
||||
R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f)
|
||||
# 3. YZ 平面双向精确切线 S 弯顺接(前后平滑连续切线顺接,严控与 P07 动间隙):
|
||||
dz_f = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0 # 3.75 mm
|
||||
R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f) # 17.283 mm
|
||||
p_mid_f = (163.0 - dy_f, z_top - dz_f)
|
||||
|
||||
theta_max_f = math.asin(dz_f / R_f)
|
||||
@@ -588,9 +585,9 @@ def p_humerus_bone(j3, j4):
|
||||
p_quarter_f2 = (C_f2[0] + R_f * math.cos(theta_max_f / 2.0),
|
||||
C_f2[1] - R_f * math.sin(theta_max_f / 2.0))
|
||||
|
||||
dz_r = (z_top - z_bot) / 2.0
|
||||
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0
|
||||
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r)
|
||||
dz_r = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0 # 0.5 mm
|
||||
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r) # 115.8125 mm
|
||||
p_mid_r = (93.0 - dy_r, z_top - dz_r)
|
||||
|
||||
theta_max_r = math.asin(dz_r / R_r)
|
||||
@@ -619,31 +616,16 @@ def p_humerus_bone(j3, j4):
|
||||
body = cq.Shape(c_int.Shape())
|
||||
s = s.fuse(body)
|
||||
|
||||
# 4. 电机定子散热 - 增加间隙
|
||||
s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
|
||||
# 4. 电机中间定子散热裸露切除 (100% 裸露)
|
||||
s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
|
||||
|
||||
# 5. M4接口成型 - 增加间隙
|
||||
s = j4.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5)
|
||||
# 5. 保持腰身为坚固完整实体,移除侧向镂空贯穿槽
|
||||
|
||||
# 6. M4 接口成型
|
||||
s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5)
|
||||
s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j4.pocket(s)
|
||||
s = make_direct_hub(s, j3, r_disc=35.0, z_out=58.0)
|
||||
|
||||
# 添加圆角过渡
|
||||
try:
|
||||
# 主体过渡区域的圆角
|
||||
s = s.edges(cq.selectors.BoxSelector(
|
||||
(X_ARM - 20, Y_AXIS - 40, z_bot - 5),
|
||||
(X_ARM + 20, Y_AXIS + 40, z_bot + 15)
|
||||
)).fillet(FILLET_MEDIUM)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# 外轮廓倒角
|
||||
s = s.edges("|Y and >Y").chamfer(CHAMFER_EDGE)
|
||||
except:
|
||||
pass
|
||||
|
||||
return s
|
||||
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
5. 组合姿态扫描:每个关节置于其极限,再扫其余关节,收敛出滑块安全范围;
|
||||
6. 写 docs/travel.json(查看器滑块范围)+ docs/travel_detail.json(逐对明细)。
|
||||
|
||||
网格化预筛(用户要求,2026-09):
|
||||
- 每零件先按 0.5mm 容差网格化取顶点云(凸包顶点集);
|
||||
- 候选对先跑 GJK(Minkowski 差 + 0.8mm 安全外扩):只有"凸包外扩后仍分离"
|
||||
才允许跳过精确布尔 —— 该判据数学上保守(网格内缩 ≤0.5mm < 外扩 0.8mm),
|
||||
其余情况(可能干涉/数值退化)一律回落 OCCT 实体会交,结果与纯布尔完全一致;
|
||||
- --no-gjk 可关闭预筛(对比校验用)。
|
||||
|
||||
并行化(用户要求):
|
||||
- mp.Pool 每个工作进程独立构建一次模型,角度任务批量分发(imap 保序);
|
||||
- OCCT 布尔运算 SetRunParallel(True);
|
||||
@@ -17,7 +24,7 @@
|
||||
任何角度都不可能碰撞,直接跳过(对单关节扫描是精确安全的)。
|
||||
|
||||
运行:python src/sweep_v2.py [--workers 20] [--step 2] [--combo-step 6]
|
||||
[--no-combos] [--joints J2,J4]
|
||||
[--no-combos] [--joints J2,J4] [--no-gjk]
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
@@ -25,6 +32,8 @@ import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
@@ -40,21 +49,132 @@ from armcore import place, bb, DOCS
|
||||
VOL_MIN = 2.0 # mm3,超过即计为干涉
|
||||
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
||||
|
||||
# 网格化预筛参数:外扩必须 ≥ 网格化偏差,判"分离"才保守可信
|
||||
MESH_TOL = 0.5 # mm,零件网格化容差
|
||||
MESH_MARGIN = 0.8 # mm,凸包安全外扩(> MESH_TOL 保证网格内缩不致漏判)
|
||||
USE_GJK = True # 进程内全局;工作进程由 initializer 传入
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 网格化 + GJK
|
||||
def tessellate_verts(shape):
|
||||
"""零件 → 顶点云 (N,3);凸包 GJK 的支撑函数直接在顶点云上取极值(等价于其凸包)。
|
||||
第二步做精确凸包精简(scipy Qhull):支撑函数结果与全顶点云逐位一致,
|
||||
但电机 STEP 网格 15 万顶点 → 数百点,GJK 从毫秒级降到微秒级。"""
|
||||
try:
|
||||
vs, _ts = shape.tessellate(MESH_TOL)
|
||||
v = np.asarray([(q.x, q.y, q.z) for q in vs], dtype=np.float64)
|
||||
if v.size == 0:
|
||||
return None
|
||||
v = np.unique(v, axis=0)
|
||||
try:
|
||||
from scipy.spatial import ConvexHull
|
||||
v = v[ConvexHull(v).vertices]
|
||||
except Exception:
|
||||
pass # 精简失败则用全顶点云(仍正确,仅慢)
|
||||
return np.ascontiguousarray(v)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def gjk_separated(va, vb, margin=MESH_MARGIN, max_iter=40):
|
||||
"""凸包 GJK(带 margin):va/vb 顶点云。返回 True 仅当
|
||||
conv(va)⊕Ball(margin) 与 conv(vb) 确证分离 —— 保守,可安全跳过精确布尔。
|
||||
可能干涉 / 数值退化 / 迭代超限 一律返回 False,回落 OCCT 精确实体求交。"""
|
||||
d = vb.mean(axis=0) - va.mean(axis=0)
|
||||
nd = np.linalg.norm(d)
|
||||
d = np.array([1.0, 0.0, 0.0]) if nd < 1e-9 else d / nd
|
||||
s = [] # 单纯形顶点,s[-1] 为最新
|
||||
for _ in range(max_iter):
|
||||
pa = va[np.argmax(va @ d)]
|
||||
pb = vb[np.argmax(vb @ (-d))]
|
||||
w = pa - pb + margin * d # ⊕Ball(margin) 的支撑
|
||||
if w @ d < 0.0: # Minkowski 差不含原点 → 确证分离
|
||||
return True
|
||||
s.append(w)
|
||||
d, state = _do_simplex(s)
|
||||
if state != "go":
|
||||
return False # 'hit' 可能干涉 / 'stop' 退化 → 精确布尔
|
||||
nd = np.linalg.norm(d)
|
||||
if nd < 1e-9:
|
||||
return False # 原点贴着单纯形 → 交给精确布尔
|
||||
d = d / nd
|
||||
return False
|
||||
|
||||
|
||||
def _do_simplex(s):
|
||||
"""GJK 单纯形约化(Muratori 形式)。返回 (下一方向 d, state):
|
||||
'go' 继续 / 'hit' 原点可能在内(→ 精确布尔)/ 'stop' 数值退化(→ 精确布尔)。"""
|
||||
n = len(s)
|
||||
if n == 1:
|
||||
return -s[0], "go"
|
||||
if n == 2:
|
||||
a, b = s[1], s[0]
|
||||
ab, ao = b - a, -a
|
||||
if ab @ ao > 0.0:
|
||||
abv = np.cross(np.cross(ab, ao), ab)
|
||||
return (None, "stop") if abv @ abv < 1e-18 else (abv, "go")
|
||||
del s[0]
|
||||
return -a, "go"
|
||||
if n == 3:
|
||||
a, b, c = s[2], s[1], s[0]
|
||||
ab, ac, ao = b - a, c - a, -a
|
||||
abv = np.cross(np.cross(ab, ao), ab)
|
||||
acv = np.cross(np.cross(ac, ao), ac)
|
||||
if abv @ ao > 0.0: # AB 边外侧
|
||||
if ab @ ao > 0.0:
|
||||
del s[0]
|
||||
return (None, "stop") if abv @ abv < 1e-18 else (abv, "go")
|
||||
s[:] = [a]
|
||||
return -a, "go"
|
||||
if acv @ ao > 0.0: # AC 边外侧
|
||||
if ac @ ao > 0.0:
|
||||
del s[1]
|
||||
return (None, "stop") if acv @ acv < 1e-18 else (acv, "go")
|
||||
s[:] = [a]
|
||||
return -a, "go"
|
||||
nrm = np.cross(ab, ac)
|
||||
if nrm @ nrm < 1e-18:
|
||||
return None, "stop"
|
||||
return (nrm if nrm @ ao > 0.0 else -nrm), "go" # 三角面上/下方
|
||||
# n == 4 四面体:原点与被排除顶点异侧的面即"外侧",约化到该面
|
||||
a3, b3, c3, d3 = s[3], s[2], s[1], s[0]
|
||||
for tri, e in (((a3, b3, c3), d3), ((a3, b3, d3), c3),
|
||||
((a3, c3, d3), b3), ((b3, c3, d3), a3)):
|
||||
u, v, w = tri
|
||||
nrm = np.cross(v - u, w - u)
|
||||
if nrm @ nrm < 1e-18:
|
||||
continue
|
||||
so = (-u) @ nrm
|
||||
sd = (e - u) @ nrm
|
||||
if abs(so) < 1e-12:
|
||||
return None, "hit"
|
||||
if so * sd < 0.0:
|
||||
s[:] = [u, v, w]
|
||||
return (nrm if so > 0.0 else -nrm), "go"
|
||||
return None, "hit" # 原点在四面体内 → 可能干涉
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 模型与上下文
|
||||
def load_model():
|
||||
import arm_model_v2_rounded as AM
|
||||
import arm_model as AM
|
||||
JS = AM.build()
|
||||
parts = []
|
||||
for p in AB.PARTS:
|
||||
g = CHAIN.index(AB.GRP.get(p["name"], "root"))
|
||||
lo, hi = bb(p["shape"])
|
||||
corners = [(x, y, z) for x in (lo[0], hi[0]) for y in (lo[1], hi[1]) for z in (lo[2], hi[2])]
|
||||
parts.append(dict(name=p["name"], shape=p["shape"], g=g, c8=corners, box=(lo, hi)))
|
||||
parts.append(dict(name=p["name"], shape=p["shape"], g=g, c8=corners, box=(lo, hi),
|
||||
verts=tessellate_verts(p["shape"])))
|
||||
joints = {t: (JS[t].rear, JS[t].d) for t in CHAIN[1:]}
|
||||
return parts, joints
|
||||
|
||||
|
||||
def _trsf_verts(verts, t):
|
||||
"""顶点云按 gp_Trsf 刚体变换(3x4 矩阵)。"""
|
||||
m = np.array([[t.Value(r, c) for c in (1, 2, 3, 4)] for r in (1, 2, 3)])
|
||||
return verts @ m[:, :3].T + m[:, 3]
|
||||
|
||||
|
||||
def axis_intervals(parts, rear, d):
|
||||
"""每个零件沿关节轴的 (轴向区间, 径向最小/最大距离)。要求 d 为坐标轴方向。"""
|
||||
out = []
|
||||
@@ -151,6 +271,7 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
|
||||
t = acc[p["g"]]
|
||||
boxes.append(p["box"] if t is None else xf_c8(p["c8"], t))
|
||||
shapes = {}
|
||||
wverts = {} # 顶点云世界坐标缓存(本姿态内复用)
|
||||
viol, errs = [], []
|
||||
n = len(parts)
|
||||
for i in range(n):
|
||||
@@ -161,13 +282,24 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
|
||||
if lo == hi:
|
||||
continue
|
||||
span = [k for k in active if lo < k <= hi]
|
||||
if not span:
|
||||
# 关键:无活动关节(零位)时必须测试全部零件对 —— 相对位姿不变不代表不干涉!
|
||||
if active and not span:
|
||||
continue
|
||||
if len(span) == 1 and (i, j) in impossible[span[0]]:
|
||||
continue
|
||||
bi, bj = boxes[i], boxes[j]
|
||||
if any(bi[1][k] < bj[0][k] - 0.1 or bj[1][k] < bi[0][k] - 0.1 for k in range(3)):
|
||||
continue
|
||||
if USE_GJK: # 网格化预筛:确证分离则免精确布尔
|
||||
for key, gk in ((i, gi), (j, gj)):
|
||||
if key not in wverts:
|
||||
t = acc[gk]
|
||||
lv = parts[key]["verts"]
|
||||
wverts[key] = None if lv is None else (
|
||||
lv if t is None else _trsf_verts(lv, t))
|
||||
vi, vj = wverts[i], wverts[j]
|
||||
if vi is not None and vj is not None and gjk_separated(vi, vj):
|
||||
continue
|
||||
si = shapes.get(i)
|
||||
if si is None:
|
||||
t = acc[gi]
|
||||
@@ -193,8 +325,9 @@ _POOL = None
|
||||
_WORKERS = 4
|
||||
|
||||
|
||||
def _init_worker():
|
||||
global _CTX
|
||||
def _init_worker(use_gjk=True):
|
||||
global _CTX, USE_GJK
|
||||
USE_GJK = bool(use_gjk)
|
||||
_CTX = make_ctx()
|
||||
|
||||
|
||||
@@ -207,7 +340,7 @@ def get_pool(workers):
|
||||
global _POOL, _WORKERS
|
||||
_WORKERS = workers
|
||||
if _POOL is None and workers > 1:
|
||||
_POOL = mp.Pool(workers, initializer=_init_worker)
|
||||
_POOL = mp.Pool(workers, initializer=_init_worker, initargs=(USE_GJK,))
|
||||
return _POOL
|
||||
|
||||
|
||||
@@ -234,8 +367,11 @@ def _ctx_single():
|
||||
|
||||
# ---------------------------------------------------------------- 扫描逻辑
|
||||
def bisect(workers, k, a_free, a_jam, tol=0.2):
|
||||
n = 0
|
||||
while abs(a_jam - a_free) > tol:
|
||||
n += 1
|
||||
mid = 0.5 * (a_free + a_jam)
|
||||
print(" %s 二分#%d 区间 [%.2f, %.2f]°" % (CHAIN[k], n, a_free, a_jam), flush=True)
|
||||
if eval_angles([{k: mid}], workers)[0]:
|
||||
a_jam = mid
|
||||
else:
|
||||
@@ -244,8 +380,10 @@ def bisect(workers, k, a_free, a_jam, tol=0.2):
|
||||
|
||||
|
||||
def sweep_single(k, step, workers):
|
||||
t_start = time.time()
|
||||
jams = {}
|
||||
lo = hi = 0.0
|
||||
tag = CHAIN[k]
|
||||
for sign in (+1, -1):
|
||||
angles = [sign * step * i for i in range(1, int(180.0 / step) + 1)]
|
||||
stop, a_free = None, 0.0
|
||||
@@ -253,10 +391,20 @@ def sweep_single(k, step, workers):
|
||||
for s in range(0, len(angles), B):
|
||||
batch = angles[s:s + B]
|
||||
results = eval_angles([{k: a} for a in batch], workers)
|
||||
done = min(s + B, len(angles))
|
||||
print(" %s %s向 %d/%d 角度(%2.0f%%)[已用 %.0fs]"
|
||||
% (tag, "正" if sign > 0 else "反", done, len(angles),
|
||||
100.0 * done / len(angles), time.time() - t_start), flush=True)
|
||||
for a, viol in zip(batch, results):
|
||||
if viol:
|
||||
jams[a] = viol
|
||||
stop = bisect(workers, k, sign * (a - step), a)
|
||||
print(" %s %s向在 %.1f° 首次干涉:%s"
|
||||
% (tag, "正" if sign > 0 else "反", a,
|
||||
"; ".join("%sx%s(%.0fmm3)" % (p, q, v) for p, q, v, _c in viol[:3])),
|
||||
flush=True)
|
||||
# 二分区间 = [本方向最后自由角, 首个卡死角] —— 修复原 sign*(a-step)
|
||||
# 在负方向算出未测过的另一侧角度、把二分踢进远端慢区的问题
|
||||
stop = bisect(workers, k, a_free, a)
|
||||
break
|
||||
a_free = a
|
||||
if stop is not None:
|
||||
@@ -267,12 +415,15 @@ def sweep_single(k, step, workers):
|
||||
hi = stop
|
||||
else:
|
||||
lo = stop
|
||||
print(" => %s 硬限位 [%7.2f, %7.2f],耗时 %.0fs" % (tag, lo, hi, time.time() - t_start),
|
||||
flush=True)
|
||||
return dict(lo=lo, hi=hi, jams=jams)
|
||||
|
||||
|
||||
def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
|
||||
jams = {}
|
||||
res = {}
|
||||
tag = "%s@%.0f° 扫%s" % (CHAIN[k_hold], e_hold, CHAIN[j_sweep])
|
||||
for sign, lim in ((+1, a_hi), (-1, a_lo)):
|
||||
angles = [sign * step * i for i in range(1, int(abs(lim) / step) + 1)] if abs(lim) > 1e-6 else []
|
||||
stop, a_free = None, 0.0
|
||||
@@ -280,10 +431,14 @@ def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
|
||||
for s in range(0, len(angles), B):
|
||||
batch = angles[s:s + B]
|
||||
results = eval_angles([{k_hold: e_hold, j_sweep: a} for a in batch], workers)
|
||||
done = min(s + B, len(angles))
|
||||
print(" %s %s向 %d/%d(%2.0f%%)"
|
||||
% (tag, "正" if sign > 0 else "反", done, len(angles),
|
||||
100.0 * done / max(len(angles), 1)), flush=True)
|
||||
for a, viol in zip(batch, results):
|
||||
if viol:
|
||||
jams[a] = viol
|
||||
lo_f, hi_j = sign * (a - step), a
|
||||
lo_f, hi_j = a_free, a # 同 sweep_single:用本方向最后自由角
|
||||
while abs(hi_j - lo_f) > 0.5:
|
||||
mid = 0.5 * (lo_f + hi_j)
|
||||
if eval_angles([{k_hold: e_hold, j_sweep: mid}], workers)[0]:
|
||||
@@ -306,7 +461,28 @@ def main():
|
||||
step, combo_step = 2.0, 6.0
|
||||
do_combos = "--no-combos" not in sys.argv
|
||||
want_joints = None
|
||||
workers = max(1, min(20, (os.cpu_count() or 4) - 2))
|
||||
if "--no-gjk" in sys.argv:
|
||||
global USE_GJK
|
||||
USE_GJK = False
|
||||
# 内存感知的默认进程数:每个工作进程预留 ~2.5GB 峰值(建模+布尔瞬时开销)
|
||||
avail_gb = 16.0
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
class _MS(ctypes.Structure):
|
||||
_fields_ = [("dwLength", ctypes.c_uint32), ("dwMemoryLoad", ctypes.c_uint32),
|
||||
("ullTotalPhys", ctypes.c_uint64), ("ullAvailPhys", ctypes.c_uint64),
|
||||
("ullTotalPageFile", ctypes.c_uint64), ("ullAvailPageFile", ctypes.c_uint64),
|
||||
("ullTotalVirtual", ctypes.c_uint64), ("ullAvailVirtual", ctypes.c_uint64),
|
||||
("ullAvailExtendedVirtual", ctypes.c_uint64)]
|
||||
_ms = _MS()
|
||||
_ms.dwLength = ctypes.sizeof(_MS)
|
||||
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(_ms))
|
||||
avail_gb = _ms.ullAvailPhys / 2**30
|
||||
except Exception:
|
||||
pass
|
||||
mem_cap = max(2, int(avail_gb / 2.5))
|
||||
workers = max(1, min(12, (os.cpu_count() or 4) - 2, mem_cap))
|
||||
for i, a in enumerate(sys.argv):
|
||||
if a == "--step":
|
||||
step = float(sys.argv[i + 1])
|
||||
@@ -318,7 +494,8 @@ def main():
|
||||
want_joints = set(sys.argv[i + 1].split(","))
|
||||
|
||||
print("=" * 78)
|
||||
print("全对全动态行程扫描 v2 并行版(workers=%d,轴=human_layout,含相邻对与电机)" % workers)
|
||||
print("全对全动态行程扫描 v2 并行版(workers=%d / 可用内存 %.0fGB / 轴=human_layout / 含相邻对与电机 / GJK预筛=%s)"
|
||||
% (workers, avail_gb, "开" if USE_GJK else "关"))
|
||||
print("=" * 78, flush=True)
|
||||
if workers > 1:
|
||||
get_pool(workers) # 启动工作进程(各自并行建模,父进程不重复建模)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import cadquery as cq
|
||||
import human_layout as HL
|
||||
from arm_build import (Joint, Ycyl, bx, cyl, box, place, bb, solids_of, common_vol,
|
||||
BORE_R, CB_R, COVER_Z, HUB_M2_CLOCK, OUT, WEB, DOCS, D2R, T, R)
|
||||
from arm_model import (CLEARANCE_GAP, CHAMFER_EDGE, FILLET_LARGE, FILLET_MEDIUM, FILLET_SMALL)
|
||||
import arm_build as AB
|
||||
from human_layout import (PLATE_T, Z_WR, Z_EL, X_ARM, Y_AXIS, Y_COVER, Y_REAR, Y_HUB)
|
||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||
|
||||
def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0):
|
||||
disc = cyl(r_disc, 56.5, z_out)
|
||||
boss = cyl(17.8, 54.942, 56.5)
|
||||
shape = shape.fuse(j.L(disc)).fuse(j.L(boss))
|
||||
shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, z_out + 40.0, u, v)
|
||||
shape = shape.cut(j.L(th)).cut(j.L(cb))
|
||||
return shape
|
||||
|
||||
def p_forearm_bone_arc(j4, j5, R_arc=28.0):
|
||||
y_out = Y_HUB + PLATE_T # 174.0
|
||||
y_hub_in = HL.Y_COVER + 0.5 + CLEARANCE_GAP # 158.5
|
||||
y_front = Y_COVER - 0.5 # 155.5
|
||||
y_rear = Y_REAR - 8.0 # 92.0
|
||||
z_top_housing = Z_WR + 35.0 # 257.0
|
||||
|
||||
# 1. J5 电机前后安装环
|
||||
collar_front = Ycyl(35.0, HL.Y_COVER - 6.0, HL.Y_COVER - 0.5, X_ARM, Z_WR)
|
||||
rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR)
|
||||
s = collar_front.fuse(rear_plate)
|
||||
|
||||
# 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL)
|
||||
disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL)
|
||||
s = s.fuse(disc)
|
||||
|
||||
# 3. M5 电机壳体水平顶部顶板 (Z: Z_WR..z_top_housing, Y: y_rear..y_front)
|
||||
roof_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_rear, Z_WR)
|
||||
.lineTo(y_rear, z_top_housing)
|
||||
.lineTo(y_front, z_top_housing)
|
||||
.lineTo(y_front, Z_WR)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(roof_w.val())
|
||||
|
||||
# 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座)
|
||||
vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_hub_in, z_top_housing)
|
||||
.lineTo(y_out, z_top_housing)
|
||||
.lineTo(y_out, Z_EL)
|
||||
.lineTo(y_hub_in, Z_EL)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(vert_leg.val())
|
||||
|
||||
# 5. L型内侧圆弧加强过渡(Circular Arc Gusset)
|
||||
# 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R_arc)
|
||||
# 起点在顶板平整面 (y_arc_start, z_top_housing),精确相切至立柱内侧面 (y_hub_in, z_arc_end)
|
||||
# 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起!
|
||||
y_arc_start = y_hub_in - R_arc
|
||||
z_arc_end = z_top_housing + R_arc
|
||||
# 45度方向内凹中点
|
||||
p_mid_arc = (y_arc_start + R_arc * (1.0 - math.sqrt(0.5)),
|
||||
z_arc_end - R_arc * (1.0 - math.sqrt(0.5)))
|
||||
|
||||
gusset_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||
.moveTo(y_arc_start, z_top_housing)
|
||||
.threePointArc(p_mid_arc, (y_hub_in, z_arc_end))
|
||||
.lineTo(y_out, z_arc_end)
|
||||
.lineTo(y_out, z_top_housing)
|
||||
.lineTo(y_arc_start, z_top_housing)
|
||||
.close()
|
||||
.extrude(35.0, both=True))
|
||||
s = s.fuse(gusset_w.val())
|
||||
|
||||
# 6. 下级 P08 旋转圆盘动态避让切除(避让下级 J5 旋转部件)
|
||||
# 注意:切除仅限于 y >= 156.0 区域,同心 R=35.5mm 保证 0.5mm 动间隙,不破坏顶板平整面
|
||||
p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR)
|
||||
s = s.cut(p08_clearance)
|
||||
|
||||
# 7. M5 电机安装接口与定子空腔
|
||||
s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5)
|
||||
s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0)
|
||||
s = j5.pocket(s)
|
||||
s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, y_rear + 0.5, y_front - 6.0, X_ARM, Z_WR))
|
||||
|
||||
# 半包散热设计:-Z底部电机定子通风口
|
||||
vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0)
|
||||
s = s.cut(vent_cut)
|
||||
|
||||
# 8. J4 直连法兰结构(凸台延伸至 60.0 跨过间隙与法兰盘牢固熔接,保持单实体)
|
||||
boss = cyl(17.8, 54.942, 60.0)
|
||||
s = s.fuse(j4.L(boss))
|
||||
s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0)))
|
||||
for a in AB.HUB_M2_CLOCK:
|
||||
u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R)
|
||||
v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R)
|
||||
th = cyl(1.4, 54.0, 58.0, u, v)
|
||||
cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v)
|
||||
s = s.cut(j4.L(th)).cut(j4.L(cb))
|
||||
|
||||
# 9. 工艺走线通孔
|
||||
slot_z = (Z_EL + z_top_housing) / 2.0
|
||||
slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z)
|
||||
s = s.cut(slot)
|
||||
|
||||
return s
|
||||
|
||||
if __name__ == "__main__":
|
||||
import arm_model as AM
|
||||
motor = cq.importers.importStep(AB.SRC_STEP).val()
|
||||
motor = place(motor, T(0, 0, -2.57))
|
||||
AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None
|
||||
|
||||
JS = {}
|
||||
for tag, cn, rear, d, xd, holder, driven in HL.JOINTS:
|
||||
JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0)
|
||||
j4 = JS["J4"]
|
||||
j5 = JS["J5"]
|
||||
j6 = JS["J6"]
|
||||
|
||||
print("Building P07 with circular arc (R=28)...")
|
||||
p07 = p_forearm_bone_arc(j4, j5, R_arc=28.0)
|
||||
print("P07 solids:", len(solids_of(p07)), "vol:", p07.Volume() / 1000.0)
|
||||
|
||||
print("Building P08...")
|
||||
p08 = AM.p_wrist_bracket(j5, j6)
|
||||
print("P08 solids:", len(solids_of(p08)), "vol:", p08.Volume() / 1000.0)
|
||||
|
||||
# Static collision between P07 and P08
|
||||
v_col = common_vol(p07, p08)
|
||||
print("Static collision P07 x P08 vol:", v_col)
|
||||
|
||||
# Dynamic sweep of J5 (wrist pitch)
|
||||
max_v = 0.0
|
||||
worst_deg = 0.0
|
||||
for deg in range(-105, 106, 5):
|
||||
# Rotate P08 around J5 axis: (X_ARM, Y_REAR, Z_WR), axis (0, 1, 0)
|
||||
p08_rot = place(p08, AB.R(deg, 0, 1, 0, X_ARM, HL.Y_REAR, Z_WR))
|
||||
cv = common_vol(p07, p08_rot)
|
||||
if cv > max_v:
|
||||
max_v = cv
|
||||
worst_deg = deg
|
||||
if cv > 2.0:
|
||||
c = BRepAlgoAPI_Common(p07.wrapped, p08_rot.wrapped)
|
||||
c.Build()
|
||||
cb = cq.Shape(c.Shape())
|
||||
lo, hi = bb(cb)
|
||||
print(f"COLLISION at deg={deg}: {cv:.1f} mm3 at lo={lo} hi={hi}")
|
||||
print(f"J5 dynamic sweep (-105..+105): max_v = {max_v:.2f} mm3 at deg = {worst_deg}")
|
||||
@@ -164,7 +164,7 @@ if __name__ == "__main__":
|
||||
print(f" Solid {i}: vol={sol.Volume()/1000.0:.2f} cm3, X=[{bb.xmin:.1f}, {bb.xmax:.1f}], Y=[{bb.ymin:.1f}, {bb.ymax:.1f}], Z=[{bb.zmin:.1f}, {bb.zmax:.1f}]")
|
||||
|
||||
# Full assembly test
|
||||
from arm_model_v2_rounded import p_chest, p_base_foot, p_girdle, p_fork, p_shoulder_roll_base, p_humerus_bone, p_wrist_bracket, p_tool_flange
|
||||
from arm_model import p_chest, p_base_foot, p_girdle, p_fork, p_shoulder_roll_base, p_humerus_bone, p_wrist_bracket, p_tool_flange
|
||||
print("\nBuilding adjacent parts for collision test...")
|
||||
p06 = p_humerus_bone(JS["J3"], JS["J4"])
|
||||
p08 = p_wrist_bracket(JS["J5"], JS["J6"])
|
||||
|
||||