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
|
```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_胸板",
|
"件号": "P01_胸板",
|
||||||
"名称": "胸腔支撑立柱(V2高刚性箱型梁+榫卯台座+工艺走线窗)",
|
"名称": "胸腔支撑立柱(高刚性箱型梁+榫卯台座+走线窗)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 847.7,
|
"体积cm3": 809.6,
|
||||||
"质量g_PLA": 1051.2,
|
"质量g_PLA": 1003.9,
|
||||||
"包络": "[-60.0 14.0 22.0 .. 6.0 80.0 470.0] (66.0x66.0x448.0)",
|
"包络": "[-148.0 95.0 22.0 .. -82.0 161.0 470.0] (66.0x66.0x448.0)",
|
||||||
"说明": "V2高刚度立柱:66x66mm厚壁封闭箱梁+8mm壁厚+顶部P03榫卯台座+两组大圆角工艺走线窗"
|
"说明": "V2高刚度立柱:66x66mm厚壁封闭箱梁+8mm壁厚+顶部P03榫卯台座+两组大圆角工艺走线窗"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"件号": "P02_底脚",
|
"件号": "P02_底脚",
|
||||||
"名称": "测试台架底座(V2高刚度抗倾覆底盘+嵌装定位槽)",
|
"名称": "测试台架底座(高刚度抗倾覆底盘+嵌装定位槽)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 1305.9,
|
"体积cm3": 1166.9,
|
||||||
"质量g_PLA": 1619.3,
|
"质量g_PLA": 1446.9,
|
||||||
"包络": "[-121.3 -91.3 -0.0 .. 81.3 231.3 22.0] (202.6x322.6x22.0)",
|
"包络": "[-186.3 16.7 -0.0 .. 76.3 239.3 22.0] (262.6x222.6x22.0)",
|
||||||
"说明": "V2加长大底盘:200x320mm重型防倾覆底座+12mm嵌装槽+4xM6夹持槽+底部走线槽"
|
"说明": "V2加长大底盘:200x320mm重型防倾覆底座+12mm嵌装槽+4xM6夹持槽+底部走线槽"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"件号": "P03_肩吊臂",
|
"件号": "P03_肩吊臂",
|
||||||
"名称": "肩关节基座机壳(V2高刚性规整风道散热版)",
|
"名称": "肩关节基座机壳(高刚性规整风道散热版)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 386.5,
|
"体积cm3": 745.3,
|
||||||
"质量g_PLA": 479.2,
|
"质量g_PLA": 924.2,
|
||||||
"包络": "[-59.0 16.0 455.0 .. 5.0 167.5 649.5] (64.0x151.5x194.5)",
|
"包络": "[-148.0 91.0 455.0 .. 2.0 165.0 647.0] (150.0x74.0x192.0)",
|
||||||
"说明": "V2浑然一体散热版:Ø73mm机舱+7组轴向散热格栅+PCBA对流窗+一体化顺滑斜挑立柱"
|
"说明": "V2浑然一体散热版:Ø73mm机舱+7组轴向散热格栅+PCBA对流窗+一体化顺滑斜挑立柱"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"件号": "P04_肩叉",
|
"件号": "P04_肩叉",
|
||||||
"名称": "肩叉(V2深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
|
"名称": "肩叉(深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 347.3,
|
"体积cm3": 347.3,
|
||||||
"质量g_PLA": 430.7,
|
"质量g_PLA": 430.7,
|
||||||
"包络": "[-16.5 90.0 455.0 .. 53.5 166.0 648.0] (70.0x76.0x193.0)",
|
"包络": "[-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_大臂旋转座",
|
"件号": "P05_大臂旋转座",
|
||||||
"名称": "大臂旋转座(V2深度圆角版:16层Loft+连接处R=15mm)",
|
"名称": "大臂旋转座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 162.9,
|
"体积cm3": 162.9,
|
||||||
"质量g_PLA": 202.0,
|
"质量g_PLA": 202.0,
|
||||||
"包络": "[-16.5 93.0 389.0 .. 53.5 174.0 525.0] (70.0x81.0x136.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_大臂骨架",
|
"件号": "P06_大臂骨架",
|
||||||
"名称": "大臂骨架(V2深度圆角版:大圆弧收腰+外轮廓R=10mm)",
|
"名称": "大臂骨架(平整版+前侧圆形过渡:顶部R10.5圆弧卷入)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 165.8,
|
"体积cm3": 182.9,
|
||||||
"质量g_PLA": 205.5,
|
"质量g_PLA": 226.8,
|
||||||
"包络": "[-16.5 91.8 297.0 .. 53.5 163.0 390.1] (70.0x71.2x93.1)",
|
"包络": "[-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_小臂骨架",
|
"件号": "P07_小臂骨架",
|
||||||
"名称": "小臂骨架(V2 L型高刚度折角版:+Y立柱+折角刚性连接+半包散热)",
|
"名称": "小臂骨架(L型圆弧加强版:R=28mm双相切内凹大圆弧+消除悬空块)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 176.8,
|
"体积cm3": 173.6,
|
||||||
"质量g_PLA": 219.3,
|
"质量g_PLA": 215.3,
|
||||||
"包络": "[-16.5 92.0 187.0 .. 53.5 174.0 367.0] (70.0x82.0x180.0)",
|
"包络": "[-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_腕偏转座",
|
"件号": "P08_腕偏转座",
|
||||||
"名称": "腕部转接座(V2深度圆角版:16层Loft+连接处R=15mm)",
|
"名称": "腕部转接座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 163.1,
|
"体积cm3": 163.1,
|
||||||
"质量g_PLA": 202.3,
|
"质量g_PLA": 202.3,
|
||||||
"包络": "[-16.5 93.0 120.0 .. 53.5 174.0 257.0] (70.0x81.0x137.0)",
|
"包络": "[-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_工具法兰",
|
"件号": "P09_工具法兰",
|
||||||
"名称": "末端工具法兰(V2深度圆角版:外轮廓R=3mm)",
|
"名称": "末端工具法兰(深度圆角版:外轮廓R=3mm)",
|
||||||
"类型": "3D打印",
|
"类型": "3D打印",
|
||||||
"数量": 1,
|
"数量": 1,
|
||||||
"体积cm3": 98.8,
|
"体积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",
|
"axis": "J1",
|
||||||
"lo": -2.0,
|
"lo": 0.0,
|
||||||
"hi": 179.0,
|
"hi": 179.0,
|
||||||
"continuous": false,
|
"continuous": false,
|
||||||
"zero": 0.0,
|
"zero": 0.0,
|
||||||
"note": "正向 +179° / 反向 -2°,总行程 181°(全对全动态扫描验证,含相邻件与电机)"
|
"note": "正向 +179° / 反向 0°,总行程 179°(全对全动态扫描验证,含相邻件与电机)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"axis": "J2",
|
"axis": "J2",
|
||||||
"lo": -112.0,
|
"lo": -114.0,
|
||||||
"hi": 116.0,
|
"hi": 113.0,
|
||||||
"continuous": false,
|
"continuous": false,
|
||||||
"zero": 0.0,
|
"zero": 0.0,
|
||||||
"note": "正向 +116° / 反向 -112°,总行程 228°(全对全动态扫描验证,含相邻件与电机)"
|
"note": "正向 +113° / 反向 -114°,总行程 227°(全对全动态扫描验证,含相邻件与电机)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"axis": "J3",
|
"axis": "J3",
|
||||||
"lo": -180.0,
|
"lo": -108.0,
|
||||||
"hi": 180.0,
|
"hi": 152.0,
|
||||||
"continuous": true,
|
"continuous": false,
|
||||||
"zero": 0.0,
|
"zero": 0.0,
|
||||||
"note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)"
|
"note": "正向 +152° / 反向 -108°,总行程 260°(全对全动态扫描验证,含相邻件与电机)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"axis": "J4",
|
"axis": "J4",
|
||||||
"lo": -105.0,
|
"lo": -104.0,
|
||||||
"hi": 105.0,
|
"hi": 104.0,
|
||||||
"continuous": false,
|
"continuous": false,
|
||||||
"zero": 0.0,
|
"zero": 0.0,
|
||||||
"note": "正向 +105° / 反向 -105°,总行程 210°(L型骨架消除轴向干涉,行程近翻倍)"
|
"note": "正向 +104° / 反向 -104°,总行程 208°(全对全动态扫描验证,含相邻件与电机)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"axis": "J5",
|
"axis": "J5",
|
||||||
"lo": -101.0,
|
"lo": -76.0,
|
||||||
"hi": 101.0,
|
"hi": 76.0,
|
||||||
"continuous": false,
|
"continuous": false,
|
||||||
"zero": 0.0,
|
"zero": 0.0,
|
||||||
"note": "正向 +101° / 反向 -101°,总行程 202°(全对全动态扫描验证,含相邻件与电机)"
|
"note": "正向 +76° / 反向 -76°,总行程 152°(全对全动态扫描验证,含相邻件与电机)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"axis": "J6",
|
"axis": "J6",
|
||||||
|
|||||||
@@ -584,14 +584,27 @@ def write_step(items, path):
|
|||||||
return os.path.getsize(path)
|
return os.path.getsize(path)
|
||||||
|
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
def export(JS):
|
def export(JS):
|
||||||
for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS):
|
for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS):
|
||||||
os.makedirs(d, exist_ok=True)
|
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,极速提速)
|
# 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):
|
def export_one_part(p):
|
||||||
|
nonlocal done_exp
|
||||||
|
t0 = time.time()
|
||||||
fn = "%s/%s.step" % (OUT + "/parts_step", p["name"])
|
fn = "%s/%s.step" % (OUT + "/parts_step", p["name"])
|
||||||
# 如果是非打印标准已有件且已存在,跳过冗余写盘
|
# 如果是非打印标准已有件且已存在,跳过冗余写盘
|
||||||
if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000:
|
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"]:
|
if p["printed"]:
|
||||||
stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"])
|
stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"])
|
||||||
cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL")
|
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"]
|
return p["name"]
|
||||||
|
|
||||||
workers = min(8, os.cpu_count() or 4)
|
workers = min(8, os.cpu_count() or 4)
|
||||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||||
list(ex.map(export_one_part, PARTS))
|
list(ex.map(export_one_part, PARTS))
|
||||||
|
|
||||||
|
print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True)
|
||||||
|
|
||||||
# 2. 多线程并行生成网页 3D Mesh
|
# 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):
|
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"])
|
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)
|
defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055)
|
||||||
v, t = mesh_of(p["shape"], defl)
|
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)
|
return (p, v, t)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||||
@@ -637,25 +672,49 @@ def export(JS):
|
|||||||
数量=1, 体积cm3=round(p["vol"] / 1000.0, 1),
|
数量=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]
|
质量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)
|
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
|
return bom
|
||||||
|
|
||||||
|
|
||||||
def collide():
|
def collide():
|
||||||
ps = [p for p in PARTS if p["vol"] > 1.0]
|
ps = [p for p in PARTS if p["vol"] > 1.0]
|
||||||
pairs = []
|
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 i in range(len(ps)):
|
||||||
for j in range(i + 1, len(ps)):
|
for j in range(i + 1, len(ps)):
|
||||||
a, b = ps[i], ps[j]
|
a, b = ps[i], ps[j]
|
||||||
if a["name"].startswith("M") and b["name"].startswith("M"):
|
if a["name"].startswith("M") and b["name"].startswith("M"):
|
||||||
continue
|
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"])
|
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)):
|
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
|
continue
|
||||||
pairs.append((a, b))
|
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):
|
def check_pair(pair):
|
||||||
|
nonlocal done_pair
|
||||||
a, b = pair
|
a, b = pair
|
||||||
|
t0 = time.time()
|
||||||
v = common_vol(a["shape"], b["shape"])
|
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:
|
if v > 2.0:
|
||||||
try:
|
try:
|
||||||
c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped)
|
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 (a["name"], b["name"], round(v, 1), loc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
workers = min(8, os.cpu_count() or 4)
|
rows = []
|
||||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
for pair in pairs:
|
||||||
results = list(ex.map(check_pair, 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])
|
rows.sort(key=lambda r: -r[2])
|
||||||
json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"),
|
json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"),
|
||||||
ensure_ascii=False)
|
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
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,19 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|||||||
from armcore import WEB, DOCS, WORKSPACE
|
from armcore import WEB, DOCS, WORKSPACE
|
||||||
SRC = os.path.dirname(os.path.abspath(__file__))
|
SRC = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
def rebuild_viewer():
|
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()
|
three = io.open(WEB + "/three_inline.js", encoding="utf-8").read()
|
||||||
assert len(three) > 400000, len(three)
|
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()
|
data = io.open(WEB + "/mesh_arm.json", encoding="utf-8").read()
|
||||||
sheets = io.open(WEB + "/sheets.jsdata", 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(
|
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(
|
trv = io.open(DOCS + "/travel.json", encoding="utf-8").read() if os.path.exists(
|
||||||
DOCS + "/travel.json") else "[]"
|
DOCS + "/travel.json") else "[]"
|
||||||
tpl = io.open(SRC + "/viewer_tpl.html", encoding="utf-8").read()
|
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)
|
out = (tpl.replace("/*__THREE__*/", three)
|
||||||
.replace("/*__DATA__*/", data)
|
.replace("/*__DATA__*/", data)
|
||||||
.replace("/*__SHEETS__*/", sheets)
|
.replace("/*__SHEETS__*/", sheets)
|
||||||
@@ -33,8 +46,9 @@ def rebuild_viewer():
|
|||||||
io.open(dst, "w", encoding="utf-8").write(out)
|
io.open(dst, "w", encoding="utf-8").write(out)
|
||||||
io.open(WEB + "/index.html", "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)
|
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)" % (
|
sz_mb = os.path.getsize(dst) / 1e6
|
||||||
dst, WORKSPACE, os.path.getsize(dst) / 1e6, len(three) / 1024, len(data) / 1024, len(sheets) / 1024))
|
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__":
|
if __name__ == "__main__":
|
||||||
rebuild_viewer()
|
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,41 +545,38 @@ def p_shoulder_roll_base(j2, j3, half=None):
|
|||||||
|
|
||||||
|
|
||||||
def p_humerus_bone(j3, j4):
|
def p_humerus_bone(j3, j4):
|
||||||
"""优化的大臂骨架:流线型、高刚度、圆润过渡
|
"""P06_大臂骨架 (Humerus Bone):
|
||||||
关键改进:
|
受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动,
|
||||||
1. XZ轮廓采用完整大圆弧(R=400mm),消除所有折线
|
向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。
|
||||||
2. YZ双S弯过渡半径增大,确保G1连续
|
1. XZ 视图满宽 Ø70mm 直通过渡:左右两侧(X: -16.5..53.5)全宽平滑垂直到达 Z_EL,严格相切融入 M4 下圆柱,彻底消除腰身台阶断差;
|
||||||
3. 增加内部加强筋,提升抗弯刚度
|
2. YZ 视图连续相切双 S 弯有机顺接:顶端 Ø70mm 圆柱与底端前端套筒(Y=155.5)及后端安装板(Y=92.0)严格切线顺接,彻底消除水平方顶阶梯平台;
|
||||||
4. 所有边缘增加倒角/圆角
|
3. 取消侧壁贯穿开孔,消除镂空,保持骨架为浑然一体的高强度流线型实体。
|
||||||
5. 电机间隙增加至2.0mm
|
|
||||||
"""
|
"""
|
||||||
# 1. M4底部抱紧套筒
|
# 1. M4 底部抱紧套筒与后端安装板 (R=35mm)
|
||||||
rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL)
|
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)
|
front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL)
|
||||||
s = rear_m4.fuse(front_m4)
|
s = rear_m4.fuse(front_m4)
|
||||||
|
|
||||||
|
# 顶端 Disc 结合面及底端过渡高度
|
||||||
z_top = 388.5
|
z_top = 388.5
|
||||||
z_bot = Z_EL + 35.0 # 367.0
|
z_bot = Z_EL + 35.0 # 367.0
|
||||||
|
|
||||||
# 2. XZ平面优化流线轮廓:采用更大的过渡半径,完全消除台阶
|
# 2. XZ 平面满宽流线轮廓(左右两侧严格相切于底端 M4 轴线圆柱,无收腰台阶):
|
||||||
# 使用完整的圆弧过渡,而非直线段
|
|
||||||
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
|
||||||
.moveTo(-16.5, Z_EL)
|
.moveTo(-16.5, Z_EL)
|
||||||
.lineTo(-16.5, z_bot)
|
.lineTo(-16.5, z_top)
|
||||||
.threePointArc((-14.0, (z_bot + z_top) / 2.0), (-16.5, z_top))
|
|
||||||
.lineTo(53.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)
|
.lineTo(53.5, Z_EL)
|
||||||
.close())
|
.close())
|
||||||
xz_solid = xz_w.extrude(100.0, both=True).val()
|
xz_solid = xz_w.extrude(100.0, both=True).val()
|
||||||
xz_shape = cq.Shape(xz_solid.wrapped)
|
xz_shape = cq.Shape(xz_solid.wrapped)
|
||||||
|
|
||||||
# 3. YZ平面双向精确切线S弯 - 增加过渡半径
|
# 3. YZ 平面双向精确切线 S 弯顺接(前后平滑连续切线顺接,严控与 P07 动间隙):
|
||||||
dz_f = (z_top - z_bot) / 2.0
|
dz_f = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||||
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0
|
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)
|
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)
|
p_mid_f = (163.0 - dy_f, z_top - dz_f)
|
||||||
|
|
||||||
theta_max_f = math.asin(dz_f / R_f)
|
theta_max_f = math.asin(dz_f / R_f)
|
||||||
C_f1 = (Y_COVER - 0.5 + R_f, z_bot)
|
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),
|
p_quarter_f1 = (C_f1[0] - R_f * math.cos(theta_max_f / 2.0),
|
||||||
@@ -587,12 +584,12 @@ def p_humerus_bone(j3, j4):
|
|||||||
C_f2 = (163.0 - R_f, z_top)
|
C_f2 = (163.0 - R_f, z_top)
|
||||||
p_quarter_f2 = (C_f2[0] + R_f * math.cos(theta_max_f / 2.0),
|
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))
|
C_f2[1] - R_f * math.sin(theta_max_f / 2.0))
|
||||||
|
|
||||||
dz_r = (z_top - z_bot) / 2.0
|
dz_r = (z_top - z_bot) / 2.0 # 10.75 mm
|
||||||
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0
|
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)
|
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)
|
p_mid_r = (93.0 - dy_r, z_top - dz_r)
|
||||||
|
|
||||||
theta_max_r = math.asin(dz_r / R_r)
|
theta_max_r = math.asin(dz_r / R_r)
|
||||||
C_r1 = (Y_REAR - 8.0 - R_r, z_bot)
|
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),
|
p_quarter_r1 = (C_r1[0] + R_r * math.cos(theta_max_r / 2.0),
|
||||||
@@ -600,7 +597,7 @@ def p_humerus_bone(j3, j4):
|
|||||||
C_r2 = (93.0 + R_r, z_top)
|
C_r2 = (93.0 + R_r, z_top)
|
||||||
p_quarter_r2 = (C_r2[0] - R_r * math.cos(theta_max_r / 2.0),
|
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))
|
C_r2[1] - R_r * math.sin(theta_max_r / 2.0))
|
||||||
|
|
||||||
yz_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
yz_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
|
||||||
.moveTo(Y_REAR - 8.0, Z_EL)
|
.moveTo(Y_REAR - 8.0, Z_EL)
|
||||||
.lineTo(Y_COVER - 0.5, Z_EL)
|
.lineTo(Y_COVER - 0.5, Z_EL)
|
||||||
@@ -613,37 +610,22 @@ def p_humerus_bone(j3, j4):
|
|||||||
.close())
|
.close())
|
||||||
yz_solid = yz_w.extrude(50.0, both=True).val()
|
yz_solid = yz_w.extrude(50.0, both=True).val()
|
||||||
yz_shape = cq.Shape(yz_solid.wrapped)
|
yz_shape = cq.Shape(yz_solid.wrapped)
|
||||||
|
|
||||||
c_int = BRepAlgoAPI_Common(yz_shape.wrapped, xz_shape.wrapped)
|
c_int = BRepAlgoAPI_Common(yz_shape.wrapped, xz_shape.wrapped)
|
||||||
c_int.Build()
|
c_int.Build()
|
||||||
body = cq.Shape(c_int.Shape())
|
body = cq.Shape(c_int.Shape())
|
||||||
s = s.fuse(body)
|
s = s.fuse(body)
|
||||||
|
|
||||||
# 4. 电机定子散热 - 增加间隙
|
# 4. 电机中间定子散热裸露切除 (100% 裸露)
|
||||||
s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
|
s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
|
||||||
|
|
||||||
# 5. M4接口成型 - 增加间隙
|
# 5. 保持腰身为坚固完整实体,移除侧向镂空贯穿槽
|
||||||
s = j4.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.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.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 = j4.pocket(s)
|
||||||
s = make_direct_hub(s, j3, r_disc=35.0, z_out=58.0)
|
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
|
return s
|
||||||
|
|
||||||
|
|
||||||
@@ -10,6 +10,13 @@
|
|||||||
5. 组合姿态扫描:每个关节置于其极限,再扫其余关节,收敛出滑块安全范围;
|
5. 组合姿态扫描:每个关节置于其极限,再扫其余关节,收敛出滑块安全范围;
|
||||||
6. 写 docs/travel.json(查看器滑块范围)+ docs/travel_detail.json(逐对明细)。
|
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 保序);
|
- mp.Pool 每个工作进程独立构建一次模型,角度任务批量分发(imap 保序);
|
||||||
- OCCT 布尔运算 SetRunParallel(True);
|
- OCCT 布尔运算 SetRunParallel(True);
|
||||||
@@ -17,7 +24,7 @@
|
|||||||
任何角度都不可能碰撞,直接跳过(对单关节扫描是精确安全的)。
|
任何角度都不可能碰撞,直接跳过(对单关节扫描是精确安全的)。
|
||||||
|
|
||||||
运行:python src/sweep_v2.py [--workers 20] [--step 2] [--combo-step 6]
|
运行: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 json
|
||||||
import math
|
import math
|
||||||
@@ -25,6 +32,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
try:
|
try:
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
@@ -40,21 +49,132 @@ from armcore import place, bb, DOCS
|
|||||||
VOL_MIN = 2.0 # mm3,超过即计为干涉
|
VOL_MIN = 2.0 # mm3,超过即计为干涉
|
||||||
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
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():
|
def load_model():
|
||||||
import arm_model_v2_rounded as AM
|
import arm_model as AM
|
||||||
JS = AM.build()
|
JS = AM.build()
|
||||||
parts = []
|
parts = []
|
||||||
for p in AB.PARTS:
|
for p in AB.PARTS:
|
||||||
g = CHAIN.index(AB.GRP.get(p["name"], "root"))
|
g = CHAIN.index(AB.GRP.get(p["name"], "root"))
|
||||||
lo, hi = bb(p["shape"])
|
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])]
|
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:]}
|
joints = {t: (JS[t].rear, JS[t].d) for t in CHAIN[1:]}
|
||||||
return parts, joints
|
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):
|
def axis_intervals(parts, rear, d):
|
||||||
"""每个零件沿关节轴的 (轴向区间, 径向最小/最大距离)。要求 d 为坐标轴方向。"""
|
"""每个零件沿关节轴的 (轴向区间, 径向最小/最大距离)。要求 d 为坐标轴方向。"""
|
||||||
out = []
|
out = []
|
||||||
@@ -151,6 +271,7 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
|
|||||||
t = acc[p["g"]]
|
t = acc[p["g"]]
|
||||||
boxes.append(p["box"] if t is None else xf_c8(p["c8"], t))
|
boxes.append(p["box"] if t is None else xf_c8(p["c8"], t))
|
||||||
shapes = {}
|
shapes = {}
|
||||||
|
wverts = {} # 顶点云世界坐标缓存(本姿态内复用)
|
||||||
viol, errs = [], []
|
viol, errs = [], []
|
||||||
n = len(parts)
|
n = len(parts)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
@@ -161,13 +282,24 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
|
|||||||
if lo == hi:
|
if lo == hi:
|
||||||
continue
|
continue
|
||||||
span = [k for k in active if lo < k <= hi]
|
span = [k for k in active if lo < k <= hi]
|
||||||
if not span:
|
# 关键:无活动关节(零位)时必须测试全部零件对 —— 相对位姿不变不代表不干涉!
|
||||||
|
if active and not span:
|
||||||
continue
|
continue
|
||||||
if len(span) == 1 and (i, j) in impossible[span[0]]:
|
if len(span) == 1 and (i, j) in impossible[span[0]]:
|
||||||
continue
|
continue
|
||||||
bi, bj = boxes[i], boxes[j]
|
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)):
|
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
|
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)
|
si = shapes.get(i)
|
||||||
if si is None:
|
if si is None:
|
||||||
t = acc[gi]
|
t = acc[gi]
|
||||||
@@ -193,8 +325,9 @@ _POOL = None
|
|||||||
_WORKERS = 4
|
_WORKERS = 4
|
||||||
|
|
||||||
|
|
||||||
def _init_worker():
|
def _init_worker(use_gjk=True):
|
||||||
global _CTX
|
global _CTX, USE_GJK
|
||||||
|
USE_GJK = bool(use_gjk)
|
||||||
_CTX = make_ctx()
|
_CTX = make_ctx()
|
||||||
|
|
||||||
|
|
||||||
@@ -207,7 +340,7 @@ def get_pool(workers):
|
|||||||
global _POOL, _WORKERS
|
global _POOL, _WORKERS
|
||||||
_WORKERS = workers
|
_WORKERS = workers
|
||||||
if _POOL is None and workers > 1:
|
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
|
return _POOL
|
||||||
|
|
||||||
|
|
||||||
@@ -234,8 +367,11 @@ def _ctx_single():
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- 扫描逻辑
|
# ---------------------------------------------------------------- 扫描逻辑
|
||||||
def bisect(workers, k, a_free, a_jam, tol=0.2):
|
def bisect(workers, k, a_free, a_jam, tol=0.2):
|
||||||
|
n = 0
|
||||||
while abs(a_jam - a_free) > tol:
|
while abs(a_jam - a_free) > tol:
|
||||||
|
n += 1
|
||||||
mid = 0.5 * (a_free + a_jam)
|
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]:
|
if eval_angles([{k: mid}], workers)[0]:
|
||||||
a_jam = mid
|
a_jam = mid
|
||||||
else:
|
else:
|
||||||
@@ -244,8 +380,10 @@ def bisect(workers, k, a_free, a_jam, tol=0.2):
|
|||||||
|
|
||||||
|
|
||||||
def sweep_single(k, step, workers):
|
def sweep_single(k, step, workers):
|
||||||
|
t_start = time.time()
|
||||||
jams = {}
|
jams = {}
|
||||||
lo = hi = 0.0
|
lo = hi = 0.0
|
||||||
|
tag = CHAIN[k]
|
||||||
for sign in (+1, -1):
|
for sign in (+1, -1):
|
||||||
angles = [sign * step * i for i in range(1, int(180.0 / step) + 1)]
|
angles = [sign * step * i for i in range(1, int(180.0 / step) + 1)]
|
||||||
stop, a_free = None, 0.0
|
stop, a_free = None, 0.0
|
||||||
@@ -253,10 +391,20 @@ def sweep_single(k, step, workers):
|
|||||||
for s in range(0, len(angles), B):
|
for s in range(0, len(angles), B):
|
||||||
batch = angles[s:s + B]
|
batch = angles[s:s + B]
|
||||||
results = eval_angles([{k: a} for a in batch], workers)
|
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):
|
for a, viol in zip(batch, results):
|
||||||
if viol:
|
if viol:
|
||||||
jams[a] = 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
|
break
|
||||||
a_free = a
|
a_free = a
|
||||||
if stop is not None:
|
if stop is not None:
|
||||||
@@ -267,12 +415,15 @@ def sweep_single(k, step, workers):
|
|||||||
hi = stop
|
hi = stop
|
||||||
else:
|
else:
|
||||||
lo = stop
|
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)
|
return dict(lo=lo, hi=hi, jams=jams)
|
||||||
|
|
||||||
|
|
||||||
def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
|
def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
|
||||||
jams = {}
|
jams = {}
|
||||||
res = {}
|
res = {}
|
||||||
|
tag = "%s@%.0f° 扫%s" % (CHAIN[k_hold], e_hold, CHAIN[j_sweep])
|
||||||
for sign, lim in ((+1, a_hi), (-1, a_lo)):
|
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 []
|
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
|
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):
|
for s in range(0, len(angles), B):
|
||||||
batch = angles[s:s + B]
|
batch = angles[s:s + B]
|
||||||
results = eval_angles([{k_hold: e_hold, j_sweep: a} for a in batch], workers)
|
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):
|
for a, viol in zip(batch, results):
|
||||||
if viol:
|
if viol:
|
||||||
jams[a] = 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:
|
while abs(hi_j - lo_f) > 0.5:
|
||||||
mid = 0.5 * (lo_f + hi_j)
|
mid = 0.5 * (lo_f + hi_j)
|
||||||
if eval_angles([{k_hold: e_hold, j_sweep: mid}], workers)[0]:
|
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
|
step, combo_step = 2.0, 6.0
|
||||||
do_combos = "--no-combos" not in sys.argv
|
do_combos = "--no-combos" not in sys.argv
|
||||||
want_joints = None
|
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):
|
for i, a in enumerate(sys.argv):
|
||||||
if a == "--step":
|
if a == "--step":
|
||||||
step = float(sys.argv[i + 1])
|
step = float(sys.argv[i + 1])
|
||||||
@@ -318,7 +494,8 @@ def main():
|
|||||||
want_joints = set(sys.argv[i + 1].split(","))
|
want_joints = set(sys.argv[i + 1].split(","))
|
||||||
|
|
||||||
print("=" * 78)
|
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)
|
print("=" * 78, flush=True)
|
||||||
if workers > 1:
|
if workers > 1:
|
||||||
get_pool(workers) # 启动工作进程(各自并行建模,父进程不重复建模)
|
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}]")
|
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
|
# 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...")
|
print("\nBuilding adjacent parts for collision test...")
|
||||||
p06 = p_humerus_bone(JS["J3"], JS["J4"])
|
p06 = p_humerus_bone(JS["J3"], JS["J4"])
|
||||||
p08 = p_wrist_bracket(JS["J5"], JS["J6"])
|
p08 = p_wrist_bracket(JS["J5"], JS["J6"])
|
||||||
|
|||||||