CadQuery Assembly 进阶:定位、子装配与干涉检查 mini-lab

本页是 V8A/V8B 的**第三篇** :V8A 演示了 Assembly 容器如何组织多零件,V8B 演示了 BOM、爆炸图、检查清单如何**记录和归档** 装配体。本页(V8C)进一步解释:

  • Placement / Location —— 组件如何被放在装配体的某个位置

  • ** 全局坐标系 vs 局部坐标系** —— 不同坐标系的区别

  • Nested Assembly —— 把相关组件组织成子装配

  • ** 教学型干涉检查** —— 不做真实仿真,但用结构化方法判断装配关系

** 教学定位**:本页是** 教学 mini-lab**,不涉及真实工业约束求解、动画和仿真。

A. 本页解决什么问题

V8A 解决了"怎么组织多零件"

V8A 演示了 Assembly + add() + Location 的基本用法,但** 只展示了最简单的"4 个零件"装配**。

V8B 解决了"装配体如何被记录和归档"

V8B 演示了 BOM 表格、爆炸图、检查清单,但** 只关注"装配体里有什么"和"如何检查"** 。

V8C 解决"组件如何被精确放置、组织和检查"

真实工程中,组件位置不能"差不多",需要:

  • 显式计算每个组件的 Location (不只是凭感觉)

  • 用** 全局坐标系 vs 局部坐标系**理清"组件相对于什么"

  • 把相关组件组织成** 子装配** (如 bolt_pair 作为一对螺栓)

  • 用** 装配关系检查表**教学型地判断是否有干涉 / 错位

B. Placement / Location 是什么

Placement (或 Location)是 CadQuery 的核心概念之一:

  • ** 单个零件有自己的局部坐标系** (由 Workplane("XY") 等定义)

  • ** 放入装配体时** ,需要指定它** 在装配体中的位置和方向**

  • Placement ** 不改变零件本身的几何** ,只改变它在装配体中的姿态

  • ** 错误的 Placement** 会导致错位、穿插或装配关系不清

位置表达(Location)

Location 是数学表达,包括:

  • ** 平移** (Translation):Location(Vector(x, y, z))

  • ** 旋转** (Rotation):Location(Vector(...), Vector(axis_x, axis_y, axis_z))

本节 V8A 已用平移表达位置,V8C 引入"局部坐标系"概念来更清晰表达。

C. 全局坐标系 vs 局部坐标系

Assembly 坐标系与 Placement 关系图

** 核心区分**:

  • ** 全局坐标系** (Global Origin):装配体的原点 (0, 0, 0),所有组件位置都以此为参考

  • ** 局部坐标系** (Part Local Origin):每个零件自己的原点,由 Workplane 定义

全局 vs 局部坐标系

坐标系类型

作用

在支架装配中的例子

常见错误

global origin

装配体的 (0, 0, 0) 基准

base_plate 的左下角放在 (0, 0, 0)

与零件局部原点混淆

part local origin

零件 Workplane 的原点

base_plate.box() 默认在自身中心

假设局部原点 = 全局原点

assembly location

零件放入装配体时的位置

vertical_plate 的 Location(Vector(20, 0, 95))

计算错误导致错位

bolt location

螺栓的位置(要穿到底板孔)

螺栓 Location(Vector(-50, 5, -15))

螺栓没穿入孔

pin location

销钉的位置(要穿入立板孔)

销钉 Location(Vector(20, 5, 95))

销钉没穿入孔

D. Nested Assembly / 子装配

嵌套装配体结构图

Nested Assembly 是 CadQuery 提供的高级特性:

  • ** 子装配** = 把相关组件先组织成一个小组合

  • ** 主装配** = 把所有子装配和单独零件组合起来

支架装配体的子装配组织

bracket_assembly (主装配)
├── base_plate (单独零件)
├── vertical_plate (单独零件)
├── bolt_pair (子装配:2 个螺栓)
│   ├── bolt_left
│   └── bolt_right
└── side_pin (单独零件)

** 子装配的好处**:

  • ** 逻辑清晰**:2 个螺栓属于"一对",便于理解

  • ** 复用性强**:子装配可以独立保存、命名、定位

  • BOM 友好 :子装配的 quantity=1,但子装配内的零件可以单独计数

V8C 的子装配示例

下方代码(见 E 节)演示:

  • bolt_pair 是 2 个螺栓组成的子装配

  • 主装配 = base + vertical + bolt_pair + side_pin

E. 教学型代码示例

下方代码展示如何用 Assembly + Location + 子装配组织支架装配体。

完整代码

"""
嵌套装配体 — CadQuery 教学示例 (V8C)
===================================

本文件是 CAD-CAM-Technology-docs 项目的 V8C 教学示例,
配合 examples/cadquery-assembly-placement-mini-lab.rst 使用。

教学目的
--------
演示如何用:
1. 显式 Location 表达组件位置
2. 局部 vs 全局坐标系的区别
3. 子装配(bolt_pair)的组织
4. 教学型 placement 数据结构

注意
----
- 本文件是教学示例(teaching example)
- 不作为工业生产模型(not for industrial production)
- 不是真实干涉求解器(not a real interference solver)
- 螺栓/销钉尺寸是教学示意值

依赖
----
- cadquery >= 2.0
- 运行:python bracket_nested_assembly.py
"""

import cadquery as cq


# ============================================================
# 参数集中区
# ============================================================

# 底板参数
base_length = 200.0
base_width = 60.0
base_thickness = 10.0
base_hole_diameter = 8.0
base_hole_spacing = 100.0

# 立板参数
vertical_length = 40.0
vertical_height = 100.0
vertical_thickness = 10.0
vertical_hole_diameter = 6.0
vertical_hole_spacing = 50.0

# 螺栓参数
bolt_diameter = 8.0
bolt_head_diameter = 13.0
bolt_head_height = 5.0
bolt_length = 25.0

# 销钉参数
pin_diameter = 6.0
pin_length = 15.0


# ============================================================
# Placement 数据结构
# ============================================================

# 教学型 placement 表:定义每个组件的"放置语义"
# 实际工程中可序列化为 JSON / YAML
PLACEMENT_TABLE = {
    "base_plate": {
        "type": "main_part",
        "location": (0.0, 0.0, 0.0),
        "rotates": False,
        "purpose": "装配体地面",
    },
    "vertical_plate": {
        "type": "main_part",
        "location": (vertical_length / 2.0, 0.0, base_thickness + vertical_height / 2.0),
        "rotates": False,
        "purpose": "装配体立面",
    },
    "bolt_left": {
        "type": "fastener",
        "location": (-base_hole_spacing / 2.0, base_thickness / 2.0, base_thickness - bolt_length),
        "rotates": False,
        "purpose": "左侧底板固定",
    },
    "bolt_right": {
        "type": "fastener",
        "location": (base_hole_spacing / 2.0, base_thickness / 2.0, base_thickness - bolt_length),
        "rotates": False,
        "purpose": "右侧底板固定",
    },
    "side_pin": {
        "type": "fastener",
        "location": (
            vertical_length / 2.0,
            vertical_thickness / 2.0,
            base_thickness + vertical_height / 2.0 - pin_length / 2.0,
        ),
        "rotates": False,
        "purpose": "立板定位",
    },
}


# ============================================================
# 组件构造函数(与 V8A 类似)
# ============================================================

def make_base_plate():
    plate = cq.Workplane("XY").box(base_length, base_thickness, base_width)
    plate = (
        plate
        .faces(">Z")
        .workplane()
        .rect(base_hole_spacing, 0, forConstruction=True)
        .vertices()
        .hole(base_hole_diameter)
    )
    return plate


def make_vertical_plate():
    vertical = (
        cq.Workplane("XY")
        .transformed(offset=(vertical_length / 2.0, 0, base_thickness + vertical_height / 2.0))
        .box(vertical_length, vertical_thickness, vertical_height)
    )
    vertical = (
        vertical
        .faces(">Z")
        .workplane()
        .hole(vertical_hole_diameter)
    )
    return vertical


def make_bolt():
    head = cq.Workplane("XY").circle(bolt_head_diameter / 2.0).extrude(bolt_head_height)
    shaft = (
        cq.Workplane("XY")
        .workplane(offset=bolt_head_height)
        .circle(bolt_diameter / 2.0)
        .extrude(bolt_length - bolt_head_height)
    )
    return head.union(shaft)


def make_pin():
    return cq.Workplane("XY").circle(pin_diameter / 2.0).extrude(pin_length)


# ============================================================
# 子装配:bolt_pair
# ============================================================

def build_bolt_pair_subassembly():
    """构造螺栓对子装配。

    子装配 = 2 个螺栓 + 共享定位参考。
    """
    subasm = cq.Assembly(name="bolt_pair")

    bolt = make_bolt()

    # 螺栓 1:相对子装配的局部坐标
    subasm.add(
        bolt,
        name="bolt_left",
        loc=cq.Location(cq.Vector(-base_hole_spacing / 2.0, 0, 0)),
        color=cq.Color(0.8, 0.7, 0.5, 1.0),
    )

    # 螺栓 2:相对子装配的局部坐标
    subasm.add(
        bolt,
        name="bolt_right",
        loc=cq.Location(cq.Vector(base_hole_spacing / 2.0, 0, 0)),
        color=cq.Color(0.8, 0.7, 0.5, 1.0),
    )

    return subasm


# ============================================================
# 主装配
# ============================================================

def build_nested_assembly():
    """构建完整的支架装配体(带子装配)。"""
    assembly = cq.Assembly(name="bracket_nested_assembly")

    # 1. 底板
    base = make_base_plate()
    assembly.add(
        base,
        name="base_plate",
        loc=cq.Location(cq.Vector(0, 0, 0)),
        color=cq.Color(0.7, 0.7, 0.8, 1.0),
    )

    # 2. 立板
    vertical = make_vertical_plate()
    assembly.add(
        vertical,
        name="vertical_plate",
        loc=cq.Location(cq.Vector(0, 0, 0)),
        color=cq.Color(0.7, 0.8, 0.7, 1.0),
    )

    # 3. 螺栓对子装配
    bolt_pair = build_bolt_pair_subassembly()
    # 子装配作为整体加入主装配
    # 位置:螺栓对在底板上方,Y 方向位于板厚中心
    assembly.add(
        bolt_pair,
        name="bolt_pair",
        loc=cq.Location(
            cq.Vector(0, base_thickness / 2.0, base_thickness - bolt_length)
        ),
        color=cq.Color(1.0, 0.0, 0.0, 1.0),  # 红色 - 子装配标记
    )

    # 4. 销钉
    pin = make_pin()
    assembly.add(
        pin,
        name="side_pin",
        loc=cq.Location(
            cq.Vector(
                vertical_length / 2.0,
                vertical_thickness / 2.0,
                base_thickness + vertical_height / 2.0 - pin_length / 2.0,
            )
        ),
        color=cq.Color(0.6, 0.6, 0.6, 1.0),
    )

    return assembly


# ============================================================
# 主流程
# ============================================================

def main():
    print("=== CadQuery 嵌套装配体 (V8C) ===")
    print("组件结构:")
    print("  bracket_nested_assembly (主装配)")
    print("  ├── base_plate")
    print("  ├── vertical_plate")
    print("  ├── bolt_pair (子装配)")
    print("  │   ├── bolt_left")
    print("  │   └── bolt_right")
    print("  └── side_pin")
    print()
    print("Placement 数据:")
    for name, info in PLACEMENT_TABLE.items():
        print(f"  {name}: {info['location']} ({info['purpose']})")
    print()

    assembly = build_nested_assembly()
    assembly.save("bracket_nested_assembly.step")
    print("[OK] 嵌套装配体已导出: bracket_nested_assembly.step")


if __name__ == "__main__":
    main()

F. 干涉检查与间隙检查的概念

干涉(Interference) :两个组件占据同一空间。

间隙(Clearance) :组件之间需要留出的空间。

教学型判断方法

本项目**不做真实几何布尔检测** (需要 OCCT 几何内核),而是用** 结构化方法**判断:

  • ** 位置表**:每个组件的坐标是否合理

  • BOM 对照 :组件数量是否正确

  • 爆炸图 :目视检查组件是否重叠

  • checklist :逐项打勾验证

为什么不真实运行 CadQuery 做检查

  • 环境限制 :本环境缺 OCCT 依赖,CadQuery 不能真实运行

  • 教学简化 :真实干涉求解器(CAD 内核)很复杂,超出教学范围

  • 结构化方法更通用 :检查表、位置表的方法适用于任何建模工具

G. 装配关系检查表

装配关系检查表

检查项

正常表现

错误表现

可能原因

修正建议

螺栓位置

螺栓穿过底板安装孔

螺栓穿入底板实体

Location(x, y, z) 计算错误

检查 base_hole_spacing 参数

立板位置

立板与底板 L 型结合

立板悬空或重叠

vertical_offset 错误

检查 base_height + vertical_height/2

销钉位置

销钉穿过立板中心孔

销钉错位

pin_location 错误

检查 vertical_thickness/2

原点设置

装配体原点 = 底板底面中心

原点偏离

第一个 add() 没设 loc

显式 loc=Location(Vector(0, 0, 0))

子装配命名

bolt_pairfastener_group

命名为 sub1asm1

命名混乱

用语义化命名

组件数量

5(1+1+2+1)

数量不对

add() 调用次数错误

对照 BOM 表格

组件间隙

螺栓/销钉留有足够间隙

螺栓/销钉顶到底板或立板

bolt_length 过短

检查 bolt_lengthbase_thickness

H. 与 V8A/V8B 的关系

V8A:多零件表达

  • 演示 Assembly 容器的基本用法

  • 4 个零件:底板 + 立板 + 2 螺栓 + 1 销钉

  • 重点是"如何把多个独立零件放进一个容器"

V8B:BOM 与归档

  • 演示 BOM 表格、爆炸图、检查清单

  • 重点是"装配体如何被记录、拆解、归档"

  • 不涉及组件如何被精确放置

V8C:定位、子装配与干涉检查

  • 演示 Location 精确计算、局部 vs 全局坐标系、子装配

  • 重点是"组件如何被精确放置、组织和检查"

  • 教学型干涉检查(不真实运行)

V8A → V8B → V8C 是递进关系

  • V8A:能组织多零件

  • V8B:能记录和归档

  • V8C:能精确放置和检查

相关页面

I. 常见误区

V8C 常见误区

#

误区

正确做法

影响等级

1

以为移动组件就是修改零件几何

Location 只改位置,不改几何

⭐⭐⭐

2

忽略局部坐标系

显式区分 part local vs global

⭐⭐⭐

3

子装配命名不清

用语义化命名(bolt_pair 而非 sub1

⭐⭐

4

把展示爆炸图当成真实装配位置

爆炸图是教学性拉开,真实位置是合拢

⭐⭐

5

只检查 BOM,不检查位置关系

BOM 和位置关系都要检查

⭐⭐⭐

6

认为没有真实运行就不能学习装配逻辑

阅读代码 + 结构化方法足以学习

⭐⭐

7

把教学型干涉检查当成工业级仿真

教学型 ≠ 工业级,真实仿真需要 OCCT 内核

⭐⭐⭐

8

子装配的 Location 错乱

子装配的 Location 是相对主装配,子装配内零件是相对子装配

⭐⭐

前 3 个和 5/7/8 是 V8C 特有误区 ,必须避免。

J. 教学声明

本页面是 CAD/CAM 学习路径的辅助材料

  • 教学示例不考虑工业级鲁棒性

  • 螺栓 / 销钉 / 材料是教学示意值

  • 不替代商业 CAD 装配设计工具

  • 教学型干涉检查 ≠ 真实干涉求解

K. 相关页面