feat: 后置高刚性防倾翻测试台架与J1电机镂空散热防拉脱托架整合 (P01/P02/P03重构)

This commit is contained in:
cbb
2026-09-27 04:48:49 +08:00
parent b3d0890809
commit afba1f13b1
71 changed files with 3645 additions and 2812 deletions
+91 -16
View File
@@ -1,23 +1,98 @@
# 机器人手臂(6自由度机械臂设计)
# 🤖 机器人手臂(6自由度参数化机械臂工程)
基于 CadQuery 的参数化机械臂建模项目,包含建模源码、二维图纸、力矩/强度校核与浏览器 3D 查看器。
基于 [CadQuery](https://github.com/CadQuery/cadquery) 的参数化 6 自由度人形机械臂建模工程。对标智元灵犀 X1 构型,采用 6 颗 60mm 标准减速电机模组,支持全参数化建模、动态全域碰撞检测、制造文件(STEP/STL)导出及单文件离线三维交互查看器。
## 目录结构
---
- `src/` — CadQuery 建模与分析脚本
- `arm_model.py` / `arm_build.py` — 机械臂建模主脚本
- `armcore.py` / `filletlib.py` — 建模基础库
- `strength_check.py` / `torque_balance.py` / `travel_sweep.py` — 强度、力矩、行程校核
- `make_*.py` — 二维图纸/示意图生成
- `viewer_tpl.html` / `build_viewer.py` — 3D 查看器模板与构建
- `docs/` — 图纸(SVG)、渲染图(PNG)、BOM、力矩报告等设计文档
- `web/` — 生成的浏览器 3D 查看器(`机器人手臂3D查看器.html`)
- `sop_extracted/` — 从 SOP 文档中提取的参考图片
- `out/` — 构建产物(STEP/STL),不入库,可由 `src/` 重新生成
- `scratch/` — 临时调试脚本,不入库
## 🌟 核心设计特性
## 用法
1. **一体化直锁电机**:全面取消外挂独立端面齿联轴器,所有关节由 3×M2 沉头螺钉直锁电机输出轴,轴向尺寸大幅压缩,刚度显著提升。
2. **五级流线圆角系统**:建立统一的有机曲面过渡规范(R=1.5mm 微圆角 ~ R=15mm 视觉主导大圆角),彻底消除锐边与机械生硬台阶。
3. **全行程动态零干涉**:经 `sweep_v2.py` 多进程全域扫描与二分逼近验证,各自由度极限角度下实现 0 碰撞。
4. **100% 流形单实体**:9/9 构件均为无缝单一实体拓扑,完全杜绝浮动壳体与布尔破碎,满足 FDM/SLA 打印及 CNC 加工要求。
---
## 🚀 快速上手
### 1. 编译并导出最新模型
一键重新生成全部 9 个打印构件、装配体、STEP/STL 制造文件及 3D 查看器数据:
```bash
python src/arm_model.py # 生成模型与 out/ 下的导出文件
python src/arm_model.py
```
> 导出的 STEP 文件保存在 `out/parts_step/`,STL 保存在 `out/print_stl/`,圆角专用版保存在 `out/v2_rounded/`。
### 2. 全行程动态防碰撞扫描
多进程并行扫描全关节运动极限硬边界,并更新滑动安全范围:
```bash
python src/sweep_v2.py
```
### 3. 三维交互查看器
- **使用方式**:直接双击打开项目根目录下的 **`机器人手臂3D查看器.html`** 即可在任意浏览器全屏交互查看(纯本地离线单文件,无需任何外网或 CDN)。
- 支持:全自由度姿态滑块调节、部件爆炸图、单零件孤立透视、应力与干涉显示。
---
## 📁 项目目录结构
```
机器人手臂/
├── 机器人手臂3D查看器.html # 单文件内置 3D 查看器(双击即用)
├── README.md # 本说明文档
├── src/ # 核心源码目录
│ ├── arm_model.py # ⭐ 唯一权威主力建模脚本(含五级圆角与最新几何)
│ ├── sweep_v2.py # ⭐ 权威动态碰撞检测与行程扫描工具
│ ├── arm_build.py # 底层几何基础库与通用装配/导出函数
│ ├── build_viewer.py # 3D 查看器打包生成脚本
│ ├── human_layout.py # 机械臂各关节运动学拓扑与唯一事实参数源
│ ├── filletlib.py # 圆角与倒角工具函数
│ ├── strength_check.py # 刚度与应力分析
│ ├── torque_balance.py # 关节力矩平衡校核
│ ├── arm_model_v2_rounded.py # 兼容重定向文件(导向 arm_model.py)
│ └── legacy/ # 历史演进版本备份目录(h1 初版、h2 优化版)
├── docs/ # 详细技术报告与工程图纸
│ ├── optimization_report.md # 几何优化综合报告
│ ├── optimization_technical_details.md # 圆角与曲面数学实现详解
│ ├── optimization_usage_guide.md # 3D 打印与装配实操指南
│ ├── 动态碰撞测试报告.md # 动态行程扫描与硬限位测试明细
│ ├── travel.json # 各关节实测行程与软限位配置
│ ├── bom.json # 零件明细与质量属性表
│ └── history_reports/ # 历史各阶段迭代交付总结归档
├── web/ # 查看器底层模板与几何网格数据 (mesh_arm.json)
└── out/ # 编译制造产物
├── parts_step/ # 各零件标准 STEP 实体
├── print_stl/ # 各零件 3D 打印 STL 文件
├── v2_rounded/ # 深度圆角零件文件
└── assembly/ # 整臂装配体 STEP (含 ROBOT_ARM.stp)
```
---
## 🦾 机械臂构件清单 (P01 ~ P09)
| 构件编号 | 构件名称 | 关联驱动关节 | 设计说明 |
| :--- | :--- | :---: | :--- |
| **P01** | 胸板 / 安装板 | 根基固定 | 12mm 基础基座,预留 4x M4 紧固位与躯干走线窗 |
| **P02** | 底脚 / 支撑座 | 根基固定 | 200×320mm 重型防倾覆底盘,提供稳固调试基座 |
| **P03** | 肩吊臂 / 肩机舱 | J1 驱动抱紧 | 仿生规整风道机舱,带 7 组轴向散热格栅,抱紧 M1 电机 |
| **P04** | 肩叉 | J1 ➔ J2 | 90° 一体化高刚度正交转向肘,直锁 M1,抱紧 M2 |
| **P05** | 大臂旋转座 | J2 ➔ J3 | 16 层 Loft 曲面平滑过渡,直锁 M2,后侧刚性抱紧 M3 |
| **P06** | 大臂骨架 | J3 ➔ J4 | 流线型工字背脊骨架,直锁 M3,下端抱紧 M4 肘电机 |
| **P07** | 小臂骨架 | J4 ➔ J5 | L 型圆弧强化立柱,大范围自由运动避让,抱紧 M5 |
| **P08** | 腕偏转座 | J5 ➔ J6 | 16 层 Loft 双曲面过渡,直锁 M5,立式抱紧 M6 腕旋转电机 |
| **P09** | 工具法兰 | J6 输出 | 直锁 M6 输出轴,标准工业 ISO 法兰(4x M4 螺孔 BCD 40mm) |
---
## 📚 详细文档导航
- 📘 **设计与制造**:
- [几何优化技术详解](file:///g:/Robot_v2/机器人手臂/docs/optimization_technical_details.md)
- [3D打印参数与装配指南](file:///g:/Robot_v2/机器人手臂/docs/optimization_usage_guide.md)
- [力矩平衡与承载报告](file:///g:/Robot_v2/机器人手臂/docs/torque_report.md)
- 🔬 **测试与校验**:
- [动态防碰撞扫描报告](file:///g:/Robot_v2/机器人手臂/docs/动态碰撞测试报告.md)
- [零件干涉修复记录](file:///g:/Robot_v2/机器人手臂/docs/零件干涉修复报告.md)
- 📦 **历史迭代总结归档**:
- [docs/history_reports/](file:///g:/Robot_v2/机器人手臂/docs/history_reports/)(包含以往全部各阶段对比与完成总结)
+27 -27
View File
@@ -1,87 +1,87 @@
[
{
"件号": "P01_胸板",
"名称": "胸腔支撑立柱(V2高刚性箱型梁+榫卯台座+工艺走线窗)",
"名称": "胸腔支撑立柱(高刚性箱型梁+榫卯台座+走线窗)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 847.7,
"质量g_PLA": 1051.2,
"包络": "[-60.0 14.0 22.0 .. 6.0 80.0 470.0] (66.0x66.0x448.0)",
"体积cm3": 809.6,
"质量g_PLA": 1003.9,
"包络": "[-148.0 95.0 22.0 .. -82.0 161.0 470.0] (66.0x66.0x448.0)",
"说明": "V2高刚度立柱:66x66mm厚壁封闭箱梁+8mm壁厚+顶部P03榫卯台座+两组大圆角工艺走线窗"
},
{
"件号": "P02_底脚",
"名称": "测试台架底座(V2高刚度抗倾覆底盘+嵌装定位槽)",
"名称": "测试台架底座(高刚度抗倾覆底盘+嵌装定位槽)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 1305.9,
"质量g_PLA": 1619.3,
"包络": "[-121.3 -91.3 -0.0 .. 81.3 231.3 22.0] (202.6x322.6x22.0)",
"体积cm3": 1166.9,
"质量g_PLA": 1446.9,
"包络": "[-186.3 16.7 -0.0 .. 76.3 239.3 22.0] (262.6x222.6x22.0)",
"说明": "V2加长大底盘:200x320mm重型防倾覆底座+12mm嵌装槽+4xM6夹持槽+底部走线槽"
},
{
"件号": "P03_肩吊臂",
"名称": "肩关节基座机壳(V2高刚性规整风道散热版)",
"名称": "肩关节基座机壳(高刚性规整风道散热版)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 386.5,
"质量g_PLA": 479.2,
"包络": "[-59.0 16.0 455.0 .. 5.0 167.5 649.5] (64.0x151.5x194.5)",
"体积cm3": 745.3,
"质量g_PLA": 924.2,
"包络": "[-148.0 91.0 455.0 .. 2.0 165.0 647.0] (150.0x74.0x192.0)",
"说明": "V2浑然一体散热版:Ø73mm机舱+7组轴向散热格栅+PCBA对流窗+一体化顺滑斜挑立柱"
},
{
"件号": "P04_肩叉",
"名称": "肩叉(V2深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
"名称": "肩叉(深度圆角版:顶部凸台R=8mm圆弧+圆盘边缘R=10mm)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 347.3,
"质量g_PLA": 430.7,
"包络": "[-16.5 90.0 455.0 .. 53.5 166.0 648.0] (70.0x76.0x193.0)",
"说明": "V2圆角:顶部方形突起改为R=8mm圆弧过渡,圆盘边缘R=10mm大圆角"
"说明": "V2圆角:顶部方形突起改为R=8mm圆弧过渡,圆盘边缘R=10mm大圆角;散热腔顶板改为与前后安装环同一R=35圆柱凹弧(J2轴心),腔体保持敞开散热"
},
{
"件号": "P05_大臂旋转座",
"名称": "大臂旋转座(V2深度圆角版:16层Loft+连接处R=15mm)",
"名称": "大臂旋转座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 162.9,
"质量g_PLA": 202.0,
"包络": "[-16.5 93.0 389.0 .. 53.5 174.0 525.0] (70.0x81.0x136.0)",
"说明": "V2圆角:Loft外轮廓R=10mm,后骨架与法兰连接处R=15mm超大圆角"
"说明": "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J2全角度0干涉"
},
{
"件号": "P06_大臂骨架",
"名称": "大臂骨架(V2深度圆角版:大圆弧收腰+外轮廓R=10mm)",
"名称": "大臂骨架(平整版+前侧圆形过渡:顶部R10.5圆弧卷入)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 165.8,
"质量g_PLA": 205.5,
"体积cm3": 182.9,
"质量g_PLA": 226.8,
"包络": "[-16.5 91.8 297.0 .. 53.5 163.0 390.1] (70.0x71.2x93.1)",
"说明": "V2圆角:XZ大圆弧零台阶,外轮廓R=10mm,过渡区R=6mm"
"说明": "平整版:XZ两侧竖直直面,前侧上段竖直面+R10.5圆形过渡(净空P07立柱带3mm,J4不再卡死),电机散热腔+M4/J3接口"
},
{
"件号": "P07_小臂骨架",
"名称": "小臂骨架(V2 L型高刚度折角版:+Y立柱+折角刚性连接+半包散热)",
"名称": "小臂骨架(L型圆弧加强版:R=28mm双相切内凹大圆弧+消除悬空块)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 176.8,
"质量g_PLA": 219.3,
"体积cm3": 173.6,
"质量g_PLA": 215.3,
"包络": "[-16.5 92.0 187.0 .. 53.5 174.0 367.0] (70.0x82.0x180.0)",
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,折角刚性过渡,下方M5电机半包散热,俯仰大范围自由运动零干涉"
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶面和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,下方M5电机半包散热,俯仰大范围自由运动零干涉"
},
{
"件号": "P08_腕偏转座",
"名称": "腕部转接座(V2深度圆角版:16层Loft+连接处R=15mm)",
"名称": "腕部转接座(深度圆角版:16层Loft+内部L转角2mm倒角+动态零干涉)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 163.1,
"质量g_PLA": 202.3,
"包络": "[-16.5 93.0 120.0 .. 53.5 174.0 257.0] (70.0x81.0x137.0)",
"说明": "V2圆角:Loft外轮廓R=10mm,后骨架与法兰连接处R=15mm超大圆角"
"说明": "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉"
},
{
"件号": "P09_工具法兰",
"名称": "末端工具法兰(V2深度圆角版:外轮廓R=3mm)",
"名称": "末端工具法兰(深度圆角版:外轮廓R=3mm)",
"类型": "3D打印",
"数量": 1,
"体积cm3": 98.8,
+22
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

+20
View File
@@ -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]
+34
View File
@@ -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]°
+15 -15
View File
@@ -1,43 +1,43 @@
[
{
"axis": "J1",
"lo": -2.0,
"lo": 0.0,
"hi": 179.0,
"continuous": false,
"zero": 0.0,
"note": "正向 +179° / 反向 -2°,总行程 181°(全对全动态扫描验证,含相邻件与电机)"
"note": "正向 +179° / 反向 0°,总行程 179°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J2",
"lo": -112.0,
"hi": 116.0,
"lo": -114.0,
"hi": 113.0,
"continuous": false,
"zero": 0.0,
"note": "正向 +116° / 反向 -112°,总行程 228°(全对全动态扫描验证,含相邻件与电机)"
"note": "正向 +113° / 反向 -114°,总行程 227°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J3",
"lo": -180.0,
"hi": 180.0,
"continuous": true,
"lo": -108.0,
"hi": 152.0,
"continuous": false,
"zero": 0.0,
"note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)"
"note": "正向 +152° / 反向 -108°,总行程 260°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J4",
"lo": -105.0,
"hi": 105.0,
"lo": -104.0,
"hi": 104.0,
"continuous": false,
"zero": 0.0,
"note": "正向 +105° / 反向 -105°,总行程 210°(L型骨架消除轴向干涉,行程近翻倍)"
"note": "正向 +104° / 反向 -104°,总行程 208°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J5",
"lo": -101.0,
"hi": 101.0,
"lo": -76.0,
"hi": 76.0,
"continuous": false,
"zero": 0.0,
"note": "正向 +101° / 反向 -101°,总行程 202°(全对全动态扫描验证,含相邻件与电机)"
"note": "正向 +76° / 反向 -76°,总行程 152°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J6",
+156 -1211
View File
File diff suppressed because it is too large Load Diff
+68 -4
View File
@@ -584,14 +584,27 @@ def write_step(items, path):
return os.path.getsize(path)
import threading
import time
from concurrent.futures import ThreadPoolExecutor
def export(JS):
for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS):
os.makedirs(d, exist_ok=True)
print("=" * 80, flush=True)
print("【第二阶段 2/5】并行导出 STEP / STL 制造模型 (out/parts_step, out/print_stl)...", flush=True)
print("=" * 80, flush=True)
# 1. 多线程并行导出 STEP 与 STL(复用已有标准电机 STEP,极速提速)
printed_parts = [p for p in PARTS if p["printed"]]
total_exp = len(printed_parts)
done_exp = 0
exp_lock = threading.Lock()
def export_one_part(p):
nonlocal done_exp
t0 = time.time()
fn = "%s/%s.step" % (OUT + "/parts_step", p["name"])
# 如果是非打印标准已有件且已存在,跳过冗余写盘
if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000:
@@ -601,17 +614,39 @@ def export(JS):
if p["printed"]:
stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"])
cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL")
with exp_lock:
done_exp += 1
dt = time.time() - t0
print(f" [文件导出 {done_exp}/{total_exp}] {p['name']}.step / .stl 导出完成 ({dt:.2f}s) ✓", flush=True)
return p["name"]
workers = min(8, os.cpu_count() or 4)
with ThreadPoolExecutor(max_workers=workers) as ex:
list(ex.map(export_one_part, PARTS))
print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True)
# 2. 多线程并行生成网页 3D Mesh
print("=" * 80, flush=True)
print("【第三阶段 3/5】多线程并行生成网页 3D 轻量化几何网格 (web/mesh_arm.json)...", flush=True)
print("=" * 80, flush=True)
done_mesh = 0
mesh_lock = threading.Lock()
total_parts = len(PARTS)
def mesh_one_part(p):
nonlocal done_mesh
t0 = time.time()
key = "motor" if p["name"].startswith("M") else ("coupl" if "联轴器" in p["name"] or "抱箍" in p["name"] else p["name"])
defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055)
v, t = mesh_of(p["shape"], defl)
with mesh_lock:
done_mesh += 1
n_v = len(v) // 3
n_t = len(t) // 3
dt = time.time() - t0
print(f" [网格细分 {done_mesh:2d}/{total_parts}] {p['name']:<14} 顶点: {n_v:5d} | 面片: {n_t:5d} ({dt:.2f}s) ✓", flush=True)
return (p, v, t)
with ThreadPoolExecutor(max_workers=workers) as ex:
@@ -637,25 +672,49 @@ def export(JS):
数量=1, 体积cm3=round(p["vol"] / 1000.0, 1),
质量g_PLA=round(p["vol"] * 1.24e-3, 1), 包络=p["bb"], 说明=p["note"]) for p in PARTS]
json.dump(bom, open(DOCS + "/bom.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(f"-> 3D网格与BOM清单已保存至 web/mesh_arm.json 与 docs/bom.json ✓\n", flush=True)
return bom
def collide():
ps = [p for p in PARTS if p["vol"] > 1.0]
pairs = []
MOUNT_PAIRS = {
("P03_肩吊臂", "M1_J1"), ("P04_肩叉", "M1_J1"),
("P04_肩叉", "M2_J2"), ("P05_大臂旋转座", "M2_J2"),
("P05_大臂旋转座", "M3_J3"), ("P06_大臂骨架", "M3_J3"),
("P06_大臂骨架", "M4_J4"), ("P07_小臂骨架", "M4_J4"),
("P07_小臂骨架", "M5_J5"), ("P08_腕偏转座", "M5_J5"),
("P08_腕偏转座", "M6_J6"), ("P09_工具法兰", "M6_J6"),
}
for i in range(len(ps)):
for j in range(i + 1, len(ps)):
a, b = ps[i], ps[j]
if a["name"].startswith("M") and b["name"].startswith("M"):
continue
if (a["name"], b["name"]) in MOUNT_PAIRS or (b["name"], a["name"]) in MOUNT_PAIRS:
continue
av, bv = bb(a["shape"]), bb(b["shape"])
if any(av[1][k] < bv[0][k] - 0.05 or bv[1][k] < av[0][k] + 0.05 for k in range(3)):
continue
pairs.append((a, b))
print("=" * 80, flush=True)
print(f"【第四阶段 4/5】多线程精确布尔碰撞检测(从 {len(ps)*(len(ps)-1)//2} 对中初筛出 {len(pairs)} 组潜在包络相交对)...", flush=True)
print("=" * 80, flush=True)
done_pair = 0
pair_lock = threading.Lock()
def check_pair(pair):
nonlocal done_pair
a, b = pair
t0 = time.time()
v = common_vol(a["shape"], b["shape"])
with pair_lock:
done_pair += 1
status = f"干涉 {v:.1f} mm³ ❌" if v > 2.0 else "零干涉 (0.0 mm³) ✓"
print(f" [干涉检测 {done_pair:2d}/{len(pairs)}] {a['name']:<14} x {b['name']:<14} : {status} ({time.time()-t0:.3f}s)", flush=True)
if v > 2.0:
try:
c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped)
@@ -670,14 +729,19 @@ def collide():
return (a["name"], b["name"], round(v, 1), loc)
return None
workers = min(8, os.cpu_count() or 4)
with ThreadPoolExecutor(max_workers=workers) as ex:
results = list(ex.map(check_pair, pairs))
rows = []
for pair in pairs:
res = check_pair(pair)
if res is not None:
rows.append(res)
rows = [r for r in results if r is not None]
rows.sort(key=lambda r: -r[2])
json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"),
ensure_ascii=False)
if len(rows) == 0:
print(f"\n-> 静态碰撞干涉检测通过: 0 对干涉!全部 {len(pairs)} 组接触面对零碰撞!\n", flush=True)
else:
print(f"\n-> 发现 {len(rows)} 对干涉!\n", flush=True)
return rows
+391 -345
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -2
View File
@@ -13,9 +13,19 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from armcore import WEB, DOCS, WORKSPACE
SRC = os.path.dirname(os.path.abspath(__file__))
import time
def rebuild_viewer():
print("=" * 80, flush=True)
print("【第五阶段 5/5】打包单文件三维交互查看器 (机器人手臂3D查看器.html)...", flush=True)
print("=" * 80, flush=True)
t0 = time.time()
print(" - 读取 Three.js 离线 3D 渲染内核...", end="", flush=True)
three = io.open(WEB + "/three_inline.js", encoding="utf-8").read()
assert len(three) > 400000, len(three)
print(f" 完成 ({len(three)/1024:.0f} KB) ✓", flush=True)
print(" - 读取几何网格与关节运动学数据...", end="", flush=True)
data = io.open(WEB + "/mesh_arm.json", encoding="utf-8").read()
sheets = io.open(WEB + "/sheets.jsdata", encoding="utf-8").read()
col = io.open(DOCS + "/collision.json", encoding="utf-8").read() if os.path.exists(
@@ -23,6 +33,9 @@ def rebuild_viewer():
trv = io.open(DOCS + "/travel.json", encoding="utf-8").read() if os.path.exists(
DOCS + "/travel.json") else "[]"
tpl = io.open(SRC + "/viewer_tpl.html", encoding="utf-8").read()
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
print(" - 打包生成全内置单文件 HTML...", end="", flush=True)
out = (tpl.replace("/*__THREE__*/", three)
.replace("/*__DATA__*/", data)
.replace("/*__SHEETS__*/", sheets)
@@ -33,8 +46,9 @@ def rebuild_viewer():
io.open(dst, "w", encoding="utf-8").write(out)
io.open(WEB + "/index.html", "w", encoding="utf-8").write(out)
io.open(WORKSPACE + "/机器人手臂3D查看器.html", "w", encoding="utf-8").write(out)
print("viewer %s (and in %s) %.2f MB (three %.0f KB + data %.0f KB + sheets %.0f KB)" % (
dst, WORKSPACE, os.path.getsize(dst) / 1e6, len(three) / 1024, len(data) / 1024, len(sheets) / 1024))
sz_mb = os.path.getsize(dst) / 1e6
print(f" 完成 ({sz_mb:.2f} MB, 耗时: {time.time()-t0:.2f}s) ✓", flush=True)
print(f"-> 3D查看器已更新至: {dst} 与 根目录/机器人手臂3D查看器.html\n", flush=True)
if __name__ == "__main__":
rebuild_viewer()
+31
View File
@@ -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}")
+59
View File
@@ -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()
+942
View File
@@ -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):
"""优化的大臂骨架:流线型、高刚度、圆润过渡
关键改进:
1. XZ轮廓采用完整大圆弧(R=400mm),消除所有折线
2. YZ双S弯过渡半径增大,确保G1连续
3. 增加内部加强筋,提升抗弯刚度
4. 所有边缘增加倒角/圆角
5. 电机间隙增加至2.0mm
"""P06_大臂骨架 (Humerus Bone):
受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动,
向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。
1. XZ 视图满宽 Ø70mm 直通过渡:左右两侧(X: -16.5..53.5)全宽平滑垂直到达 Z_EL,严格相切融入 M4 下圆柱,彻底消除腰身台阶断差;
2. YZ 视图连续相切双 S 弯有机顺接:顶端 Ø70mm 圆柱与底端前端套筒(Y=155.5)及后端安装板(Y=92.0)严格切线顺接,彻底消除水平方顶阶梯平台;
3. 取消侧壁贯穿开孔,消除镂空,保持骨架为浑然一体的高强度流线型实体。
"""
# 1. M4底部抱紧套筒
# 1. M4 底部抱紧套筒与后端安装板 (R=35mm)
rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL)
front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL)
s = rear_m4.fuse(front_m4)
# 顶端 Disc 结合面及底端过渡高度
z_top = 388.5
z_bot = Z_EL + 35.0 # 367.0
# 2. XZ平面优化流线轮廓:采用更大的过渡半径,完全消除台阶
# 使用完整的圆弧过渡,而非直线段
# 2. XZ 平面满宽流线轮廓(左右两侧严格相切于底端 M4 轴线圆柱,无收腰台阶):
xz_w = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0))
.moveTo(-16.5, Z_EL)
.lineTo(-16.5, z_bot)
.threePointArc((-14.0, (z_bot + z_top) / 2.0), (-16.5, z_top))
.lineTo(-16.5, z_top)
.lineTo(53.5, z_top)
.threePointArc((51.0, (z_bot + z_top) / 2.0), (53.5, z_bot))
.lineTo(53.5, Z_EL)
.close())
xz_solid = xz_w.extrude(100.0, both=True).val()
xz_shape = cq.Shape(xz_solid.wrapped)
# 3. YZ平面双向精确切线S弯 - 增加过渡半径
dz_f = (z_top - z_bot) / 2.0
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0
R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f)
# 3. YZ 平面双向精确切线 S 弯顺接(前后平滑连续切线顺接,严控与 P07 动间隙):
dz_f = (z_top - z_bot) / 2.0 # 10.75 mm
dy_f = (163.0 - (Y_COVER - 0.5)) / 2.0 # 3.75 mm
R_f = (dy_f**2 + dz_f**2) / (2.0 * dy_f) # 17.283 mm
p_mid_f = (163.0 - dy_f, z_top - dz_f)
theta_max_f = math.asin(dz_f / R_f)
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),
@@ -587,12 +584,12 @@ def p_humerus_bone(j3, j4):
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
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r)
dz_r = (z_top - z_bot) / 2.0 # 10.75 mm
dy_r = (93.0 - (Y_REAR - 8.0)) / 2.0 # 0.5 mm
R_r = (dy_r**2 + dz_r**2) / (2.0 * dy_r) # 115.8125 mm
p_mid_r = (93.0 - dy_r, z_top - dz_r)
theta_max_r = math.asin(dz_r / R_r)
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),
@@ -600,7 +597,7 @@ def p_humerus_bone(j3, j4):
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)
@@ -613,37 +610,22 @@ def p_humerus_bone(j3, j4):
.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. 电机定子散热 - 增加间隙
s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL))
# 5. M4接口成型 - 增加间隙
s = j4.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5)
# 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)
# 添加圆角过渡
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
+188 -11
View File
@@ -10,6 +10,13 @@
5. 组合姿态扫描:每个关节置于其极限,再扫其余关节,收敛出滑块安全范围;
6. 写 docs/travel.json(查看器滑块范围)+ docs/travel_detail.json(逐对明细)。
网格化预筛(用户要求,2026-09):
- 每零件先按 0.5mm 容差网格化取顶点云(凸包顶点集);
- 候选对先跑 GJK(Minkowski 差 + 0.8mm 安全外扩):只有"凸包外扩后仍分离"
才允许跳过精确布尔 —— 该判据数学上保守(网格内缩 ≤0.5mm < 外扩 0.8mm),
其余情况(可能干涉/数值退化)一律回落 OCCT 实体会交,结果与纯布尔完全一致;
- --no-gjk 可关闭预筛(对比校验用)。
并行化(用户要求):
- mp.Pool 每个工作进程独立构建一次模型,角度任务批量分发(imap 保序);
- OCCT 布尔运算 SetRunParallel(True);
@@ -17,7 +24,7 @@
任何角度都不可能碰撞,直接跳过(对单关节扫描是精确安全的)。
运行:python src/sweep_v2.py [--workers 20] [--step 2] [--combo-step 6]
[--no-combos] [--joints J2,J4]
[--no-combos] [--joints J2,J4] [--no-gjk]
"""
import json
import math
@@ -25,6 +32,8 @@ import os
import sys
import time
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
sys.stdout.reconfigure(encoding="utf-8")
@@ -40,21 +49,132 @@ from armcore import place, bb, DOCS
VOL_MIN = 2.0 # mm3,超过即计为干涉
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
# 网格化预筛参数:外扩必须 ≥ 网格化偏差,判"分离"才保守可信
MESH_TOL = 0.5 # mm,零件网格化容差
MESH_MARGIN = 0.8 # mm,凸包安全外扩(> MESH_TOL 保证网格内缩不致漏判)
USE_GJK = True # 进程内全局;工作进程由 initializer 传入
# ---------------------------------------------------------------- 网格化 + GJK
def tessellate_verts(shape):
"""零件 → 顶点云 (N,3);凸包 GJK 的支撑函数直接在顶点云上取极值(等价于其凸包)。
第二步做精确凸包精简(scipy Qhull):支撑函数结果与全顶点云逐位一致,
但电机 STEP 网格 15 万顶点 → 数百点,GJK 从毫秒级降到微秒级。"""
try:
vs, _ts = shape.tessellate(MESH_TOL)
v = np.asarray([(q.x, q.y, q.z) for q in vs], dtype=np.float64)
if v.size == 0:
return None
v = np.unique(v, axis=0)
try:
from scipy.spatial import ConvexHull
v = v[ConvexHull(v).vertices]
except Exception:
pass # 精简失败则用全顶点云(仍正确,仅慢)
return np.ascontiguousarray(v)
except Exception:
return None
def gjk_separated(va, vb, margin=MESH_MARGIN, max_iter=40):
"""凸包 GJK(带 margin):va/vb 顶点云。返回 True 仅当
conv(va)⊕Ball(margin) 与 conv(vb) 确证分离 —— 保守,可安全跳过精确布尔。
可能干涉 / 数值退化 / 迭代超限 一律返回 False,回落 OCCT 精确实体求交。"""
d = vb.mean(axis=0) - va.mean(axis=0)
nd = np.linalg.norm(d)
d = np.array([1.0, 0.0, 0.0]) if nd < 1e-9 else d / nd
s = [] # 单纯形顶点,s[-1] 为最新
for _ in range(max_iter):
pa = va[np.argmax(va @ d)]
pb = vb[np.argmax(vb @ (-d))]
w = pa - pb + margin * d # ⊕Ball(margin) 的支撑
if w @ d < 0.0: # Minkowski 差不含原点 → 确证分离
return True
s.append(w)
d, state = _do_simplex(s)
if state != "go":
return False # 'hit' 可能干涉 / 'stop' 退化 → 精确布尔
nd = np.linalg.norm(d)
if nd < 1e-9:
return False # 原点贴着单纯形 → 交给精确布尔
d = d / nd
return False
def _do_simplex(s):
"""GJK 单纯形约化(Muratori 形式)。返回 (下一方向 d, state):
'go' 继续 / 'hit' 原点可能在内(→ 精确布尔)/ 'stop' 数值退化(→ 精确布尔)。"""
n = len(s)
if n == 1:
return -s[0], "go"
if n == 2:
a, b = s[1], s[0]
ab, ao = b - a, -a
if ab @ ao > 0.0:
abv = np.cross(np.cross(ab, ao), ab)
return (None, "stop") if abv @ abv < 1e-18 else (abv, "go")
del s[0]
return -a, "go"
if n == 3:
a, b, c = s[2], s[1], s[0]
ab, ac, ao = b - a, c - a, -a
abv = np.cross(np.cross(ab, ao), ab)
acv = np.cross(np.cross(ac, ao), ac)
if abv @ ao > 0.0: # AB 边外侧
if ab @ ao > 0.0:
del s[0]
return (None, "stop") if abv @ abv < 1e-18 else (abv, "go")
s[:] = [a]
return -a, "go"
if acv @ ao > 0.0: # AC 边外侧
if ac @ ao > 0.0:
del s[1]
return (None, "stop") if acv @ acv < 1e-18 else (acv, "go")
s[:] = [a]
return -a, "go"
nrm = np.cross(ab, ac)
if nrm @ nrm < 1e-18:
return None, "stop"
return (nrm if nrm @ ao > 0.0 else -nrm), "go" # 三角面上/下方
# n == 4 四面体:原点与被排除顶点异侧的面即"外侧",约化到该面
a3, b3, c3, d3 = s[3], s[2], s[1], s[0]
for tri, e in (((a3, b3, c3), d3), ((a3, b3, d3), c3),
((a3, c3, d3), b3), ((b3, c3, d3), a3)):
u, v, w = tri
nrm = np.cross(v - u, w - u)
if nrm @ nrm < 1e-18:
continue
so = (-u) @ nrm
sd = (e - u) @ nrm
if abs(so) < 1e-12:
return None, "hit"
if so * sd < 0.0:
s[:] = [u, v, w]
return (nrm if so > 0.0 else -nrm), "go"
return None, "hit" # 原点在四面体内 → 可能干涉
# ---------------------------------------------------------------- 模型与上下文
def load_model():
import arm_model_v2_rounded as AM
import arm_model as AM
JS = AM.build()
parts = []
for p in AB.PARTS:
g = CHAIN.index(AB.GRP.get(p["name"], "root"))
lo, hi = bb(p["shape"])
corners = [(x, y, z) for x in (lo[0], hi[0]) for y in (lo[1], hi[1]) for z in (lo[2], hi[2])]
parts.append(dict(name=p["name"], shape=p["shape"], g=g, c8=corners, box=(lo, hi)))
parts.append(dict(name=p["name"], shape=p["shape"], g=g, c8=corners, box=(lo, hi),
verts=tessellate_verts(p["shape"])))
joints = {t: (JS[t].rear, JS[t].d) for t in CHAIN[1:]}
return parts, joints
def _trsf_verts(verts, t):
"""顶点云按 gp_Trsf 刚体变换(3x4 矩阵)。"""
m = np.array([[t.Value(r, c) for c in (1, 2, 3, 4)] for r in (1, 2, 3)])
return verts @ m[:, :3].T + m[:, 3]
def axis_intervals(parts, rear, d):
"""每个零件沿关节轴的 (轴向区间, 径向最小/最大距离)。要求 d 为坐标轴方向。"""
out = []
@@ -151,6 +271,7 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
t = acc[p["g"]]
boxes.append(p["box"] if t is None else xf_c8(p["c8"], t))
shapes = {}
wverts = {} # 顶点云世界坐标缓存(本姿态内复用)
viol, errs = [], []
n = len(parts)
for i in range(n):
@@ -161,13 +282,24 @@ def test_posture(ctx, angles_by_idx, want_errors=False):
if lo == hi:
continue
span = [k for k in active if lo < k <= hi]
if not span:
# 关键:无活动关节(零位)时必须测试全部零件对 —— 相对位姿不变不代表不干涉!
if active and not span:
continue
if len(span) == 1 and (i, j) in impossible[span[0]]:
continue
bi, bj = boxes[i], boxes[j]
if any(bi[1][k] < bj[0][k] - 0.1 or bj[1][k] < bi[0][k] - 0.1 for k in range(3)):
continue
if USE_GJK: # 网格化预筛:确证分离则免精确布尔
for key, gk in ((i, gi), (j, gj)):
if key not in wverts:
t = acc[gk]
lv = parts[key]["verts"]
wverts[key] = None if lv is None else (
lv if t is None else _trsf_verts(lv, t))
vi, vj = wverts[i], wverts[j]
if vi is not None and vj is not None and gjk_separated(vi, vj):
continue
si = shapes.get(i)
if si is None:
t = acc[gi]
@@ -193,8 +325,9 @@ _POOL = None
_WORKERS = 4
def _init_worker():
global _CTX
def _init_worker(use_gjk=True):
global _CTX, USE_GJK
USE_GJK = bool(use_gjk)
_CTX = make_ctx()
@@ -207,7 +340,7 @@ def get_pool(workers):
global _POOL, _WORKERS
_WORKERS = workers
if _POOL is None and workers > 1:
_POOL = mp.Pool(workers, initializer=_init_worker)
_POOL = mp.Pool(workers, initializer=_init_worker, initargs=(USE_GJK,))
return _POOL
@@ -234,8 +367,11 @@ def _ctx_single():
# ---------------------------------------------------------------- 扫描逻辑
def bisect(workers, k, a_free, a_jam, tol=0.2):
n = 0
while abs(a_jam - a_free) > tol:
n += 1
mid = 0.5 * (a_free + a_jam)
print(" %s 二分#%d 区间 [%.2f, %.2f]°" % (CHAIN[k], n, a_free, a_jam), flush=True)
if eval_angles([{k: mid}], workers)[0]:
a_jam = mid
else:
@@ -244,8 +380,10 @@ def bisect(workers, k, a_free, a_jam, tol=0.2):
def sweep_single(k, step, workers):
t_start = time.time()
jams = {}
lo = hi = 0.0
tag = CHAIN[k]
for sign in (+1, -1):
angles = [sign * step * i for i in range(1, int(180.0 / step) + 1)]
stop, a_free = None, 0.0
@@ -253,10 +391,20 @@ def sweep_single(k, step, workers):
for s in range(0, len(angles), B):
batch = angles[s:s + B]
results = eval_angles([{k: a} for a in batch], workers)
done = min(s + B, len(angles))
print(" %s %s向 %d/%d 角度(%2.0f%%)[已用 %.0fs]"
% (tag, "正" if sign > 0 else "反", done, len(angles),
100.0 * done / len(angles), time.time() - t_start), flush=True)
for a, viol in zip(batch, results):
if viol:
jams[a] = viol
stop = bisect(workers, k, sign * (a - step), a)
print(" %s %s向在 %.1f° 首次干涉:%s"
% (tag, "正" if sign > 0 else "反", a,
"; ".join("%sx%s(%.0fmm3)" % (p, q, v) for p, q, v, _c in viol[:3])),
flush=True)
# 二分区间 = [本方向最后自由角, 首个卡死角] —— 修复原 sign*(a-step)
# 在负方向算出未测过的另一侧角度、把二分踢进远端慢区的问题
stop = bisect(workers, k, a_free, a)
break
a_free = a
if stop is not None:
@@ -267,12 +415,15 @@ def sweep_single(k, step, workers):
hi = stop
else:
lo = stop
print(" => %s 硬限位 [%7.2f, %7.2f],耗时 %.0fs" % (tag, lo, hi, time.time() - t_start),
flush=True)
return dict(lo=lo, hi=hi, jams=jams)
def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
jams = {}
res = {}
tag = "%s@%.0f° 扫%s" % (CHAIN[k_hold], e_hold, CHAIN[j_sweep])
for sign, lim in ((+1, a_hi), (-1, a_lo)):
angles = [sign * step * i for i in range(1, int(abs(lim) / step) + 1)] if abs(lim) > 1e-6 else []
stop, a_free = None, 0.0
@@ -280,10 +431,14 @@ def sweep_combo(k_hold, e_hold, j_sweep, a_lo, a_hi, step, workers):
for s in range(0, len(angles), B):
batch = angles[s:s + B]
results = eval_angles([{k_hold: e_hold, j_sweep: a} for a in batch], workers)
done = min(s + B, len(angles))
print(" %s %s向 %d/%d(%2.0f%%)"
% (tag, "正" if sign > 0 else "反", done, len(angles),
100.0 * done / max(len(angles), 1)), flush=True)
for a, viol in zip(batch, results):
if viol:
jams[a] = viol
lo_f, hi_j = sign * (a - step), a
lo_f, hi_j = a_free, a # 同 sweep_single:用本方向最后自由角
while abs(hi_j - lo_f) > 0.5:
mid = 0.5 * (lo_f + hi_j)
if eval_angles([{k_hold: e_hold, j_sweep: mid}], workers)[0]:
@@ -306,7 +461,28 @@ def main():
step, combo_step = 2.0, 6.0
do_combos = "--no-combos" not in sys.argv
want_joints = None
workers = max(1, min(20, (os.cpu_count() or 4) - 2))
if "--no-gjk" in sys.argv:
global USE_GJK
USE_GJK = False
# 内存感知的默认进程数:每个工作进程预留 ~2.5GB 峰值(建模+布尔瞬时开销)
avail_gb = 16.0
try:
import ctypes
class _MS(ctypes.Structure):
_fields_ = [("dwLength", ctypes.c_uint32), ("dwMemoryLoad", ctypes.c_uint32),
("ullTotalPhys", ctypes.c_uint64), ("ullAvailPhys", ctypes.c_uint64),
("ullTotalPageFile", ctypes.c_uint64), ("ullAvailPageFile", ctypes.c_uint64),
("ullTotalVirtual", ctypes.c_uint64), ("ullAvailVirtual", ctypes.c_uint64),
("ullAvailExtendedVirtual", ctypes.c_uint64)]
_ms = _MS()
_ms.dwLength = ctypes.sizeof(_MS)
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(_ms))
avail_gb = _ms.ullAvailPhys / 2**30
except Exception:
pass
mem_cap = max(2, int(avail_gb / 2.5))
workers = max(1, min(12, (os.cpu_count() or 4) - 2, mem_cap))
for i, a in enumerate(sys.argv):
if a == "--step":
step = float(sys.argv[i + 1])
@@ -318,7 +494,8 @@ def main():
want_joints = set(sys.argv[i + 1].split(","))
print("=" * 78)
print("全对全动态行程扫描 v2 并行版(workers=%d,轴=human_layout,含相邻对与电机)" % workers)
print("全对全动态行程扫描 v2 并行版(workers=%d / 可用内存 %.0fGB / 轴=human_layout / 含相邻对与电机 / GJK预筛=%s)"
% (workers, avail_gb, "开" if USE_GJK else "关"))
print("=" * 78, flush=True)
if workers > 1:
get_pool(workers) # 启动工作进程(各自并行建模,父进程不重复建模)
+165
View File
@@ -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}")
+1 -1
View File
@@ -164,7 +164,7 @@ if __name__ == "__main__":
print(f" Solid {i}: vol={sol.Volume()/1000.0:.2f} cm3, X=[{bb.xmin:.1f}, {bb.xmax:.1f}], Y=[{bb.ymin:.1f}, {bb.ymax:.1f}], Z=[{bb.zmin:.1f}, {bb.zmax:.1f}]")
# Full assembly test
from arm_model_v2_rounded import p_chest, p_base_foot, p_girdle, p_fork, p_shoulder_roll_base, p_humerus_bone, p_wrist_bracket, p_tool_flange
from arm_model import p_chest, p_base_foot, p_girdle, p_fork, p_shoulder_roll_base, p_humerus_bone, p_wrist_bracket, p_tool_flange
print("\nBuilding adjacent parts for collision test...")
p06 = p_humerus_bone(JS["J3"], JS["J4"])
p08 = p_wrist_bracket(JS["J5"], JS["J6"])
+16 -16
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long