第10章-实战案例与最佳实践
第10章:实战案例与最佳实践
10.1 机械零件设计案例
10.1.1 完整的联轴器设计
import cadquery as cq
import math
class FlexibleCoupling:
"""
弹性联轴器
包含两个半联轴器和中间的弹性元件
"""
def __init__(self, bore1, bore2, outer_diameter, length,
num_jaws=3, jaw_angle=45, jaw_depth=10):
"""
初始化联轴器参数
Args:
bore1, bore2: 两端轴孔直径
outer_diameter: 外径
length: 总长度
num_jaws: 爪数
jaw_angle: 爪角度
jaw_depth: 爪深度
"""
self.bore1 = bore1
self.bore2 = bore2
self.outer_d = outer_diameter
self.length = length
self.num_jaws = num_jaws
self.jaw_angle = jaw_angle
self.jaw_depth = jaw_depth
self.half_length = length / 2
def make_half(self, bore):
"""创建半联轴器"""
result = (
cq.Workplane("XY")
# 主体
.circle(self.outer_d / 2)
.extrude(self.half_length)
# 轴孔
.faces(">Z")
.workplane()
.hole(bore)
# 键槽
.faces("<Z")
.workplane(invert=True)
.center(bore / 2 - 1, 0)
.rect(bore * 0.25, bore * 0.2)
.cutBlind(self.half_length * 0.8)
)
# 添加爪
angle_step = 360 / self.num_jaws
for i in range(self.num_jaws):
angle = i * angle_step
result = (
result
.faces("<Z")
.workplane()
.transformed(rotate=(0, 0, angle))
.center(0, self.outer_d / 4)
.rect(self.outer_d * 0.3, self.outer_d * 0.3)
.extrude(-self.jaw_depth)
)
return result
def make_spider(self):
"""创建中间弹性元件(蜘蛛)"""
spider_thickness = self.jaw_depth * 0.8
inner_r = self.bore1 * 0.8
outer_r = self.outer_d / 2 * 0.7
result = (
cq.Workplane("XY")
.circle(outer_r)
.extrude(spider_thickness)
)
# 添加凸起
angle_step = 360 / (self.num_jaws * 2)
for i in range(self.num_jaws * 2):
angle = i * angle_step + angle_step / 2
result = (
result
.faces(">Z")
.workplane()
.transformed(rotate=(0, 0, angle))
.center(0, outer_r * 0.6)
.circle(outer_r * 0.2)
.extrude(spider_thickness * 0.3)
)
return result
def assemble(self):
"""组装联轴器"""
half1 = self.make_half(self.bore1)
half2 = self.make_half(self.bore2)
spider = self.make_spider()
assy = cq.Assembly(name="flexible_coupling")
# 第一个半联轴器
assy.add(half1, name="half1", color=cq.Color("steelblue"))
# 弹性元件
assy.add(spider, name="spider", color=cq.Color("orange"),
loc=cq.Location(cq.Vector(0, 0, self.half_length)))
# 第二个半联轴器(旋转180度)
assy.add(half2, name="half2", color=cq.Color("steelblue"),
loc=cq.Location(cq.Vector(0, 0, self.length),
cq.Vector(0, 0, 1), 180))
return assy
# 创建联轴器
coupling = FlexibleCoupling(
bore1=20, bore2=25,
outer_diameter=80,
length=60,
num_jaws=3
)
# 生成各部件
half = coupling.make_half(20)
spider = coupling.make_spider()
assembly = coupling.assemble()
# 导出
cq.exporters.export(half, "coupling_half.step")
cq.exporters.export(spider, "coupling_spider.step")
assembly.save("coupling_assembly.step")
print("联轴器设计完成!")
10.1.2 齿轮箱外壳设计
import cadquery as cq
class GearboxHousing:
"""齿轮箱外壳"""
def __init__(self, length, width, height, wall_thickness=5,
shaft_bore=30, shaft_positions=None):
self.length = length
self.width = width
self.height = height
self.wall = wall_thickness
self.shaft_bore = shaft_bore
self.shaft_positions = shaft_positions or [
(-length/4, 0),
(length/4, 0)
]
def make_base(self):
"""创建底壳"""
result = (
cq.Workplane("XY")
# 外壳
.box(self.length, self.width, self.height / 2)
.edges("|Z")
.fillet(self.wall)
# 挖空
.faces(">Z")
.shell(-self.wall)
)
# 添加轴承座
for pos in self.shaft_positions:
result = (
result
.faces("<Z[1]")
.workplane()
.center(pos[0], pos[1])
.circle(self.shaft_bore + 10)
.extrude(self.height / 2 - self.wall - 5)
.faces(">Z")
.workplane()
.center(pos[0], pos[1])
.hole(self.shaft_bore)
)
# 添加安装凸耳
ear_positions = [
(-self.length/2 + 15, -self.width/2 + 15),
(-self.length/2 + 15, self.width/2 - 15),
(self.length/2 - 15, -self.width/2 + 15),
(self.length/2 - 15, self.width/2 - 15),
]
for pos in ear_positions:
result = (
result
.faces("<Z")
.workplane(invert=True)
.center(pos[0], pos[1])
.circle(12)
.extrude(-10)
.faces("<Z")
.workplane(invert=True)
.center(pos[0], pos[1])
.cboreHole(8, 14, 5)
)
# 添加密封槽
result = (
result
.faces(">Z")
.workplane()
.rect(self.length - self.wall * 4, self.width - self.wall * 4)
.cutBlind(-3)
)
return result
def make_cover(self):
"""创建上盖"""
result = (
cq.Workplane("XY")
.box(self.length, self.width, self.height / 2)
.edges("|Z")
.fillet(self.wall)
.faces("<Z")
.shell(-self.wall)
)
# 添加轴承座(上半部分)
for pos in self.shaft_positions:
result = (
result
.faces(">Z[1]")
.workplane()
.center(pos[0], pos[1])
.circle(self.shaft_bore + 10)
.extrude(-(self.height / 2 - self.wall - 5))
.faces("<Z")
.workplane()
.center(pos[0], pos[1])
.hole(self.shaft_bore)
)
# 密封凸起
result = (
result
.faces("<Z")
.workplane()
.rect(self.length - self.wall * 4 - 1, self.width - self.wall * 4 - 1)
.extrude(-2)
)
# 螺栓孔
bolt_positions = [
(-self.length/2 + 20, -self.width/2 + 20),
(-self.length/2 + 20, self.width/2 - 20),
(self.length/2 - 20, -self.width/2 + 20),
(self.length/2 - 20, self.width/2 - 20),
(0, -self.width/2 + 15),
(0, self.width/2 - 15),
]
for pos in bolt_positions:
result = (
result
.faces(">Z")
.workplane()
.center(pos[0], pos[1])
.cboreHole(8, 14, 5)
)
return result
def assemble(self):
"""组装"""
base = self.make_base()
cover = self.make_cover()
assy = cq.Assembly(name="gearbox_housing")
assy.add(base, name="base", color=cq.Color("gray"))
assy.add(cover, name="cover", color=cq.Color("lightgray"),
loc=cq.Location(cq.Vector(0, 0, self.height / 2)))
return assy
# 创建齿轮箱外壳
housing = GearboxHousing(
length=200,
width=150,
height=100,
wall_thickness=6,
shaft_bore=35,
shaft_positions=[(-50, 0), (50, 0)]
)
# 导出
base = housing.make_base()
cover = housing.make_cover()
assy = housing.assemble()
cq.exporters.export(base, "gearbox_base.step")
cq.exporters.export(cover, "gearbox_cover.step")
assy.save("gearbox_assembly.step")
10.2 电子产品外壳设计
10.2.1 Raspberry Pi外壳
import cadquery as cq
class RaspberryPiCase:
"""Raspberry Pi 4外壳"""
# Pi 4尺寸常量(mm)
PI_LENGTH = 85
PI_WIDTH = 56
PI_HEIGHT = 20 # 包含散热片
# 接口位置
USB_OFFSET = 9
HDMI_OFFSET = 26
POWER_OFFSET = 10.6
def __init__(self, wall=2, clearance=1, vent=True):
self.wall = wall
self.clearance = clearance
self.vent = vent
# 计算外壳尺寸
self.inner_l = self.PI_LENGTH + clearance * 2
self.inner_w = self.PI_WIDTH + clearance * 2
self.inner_h = self.PI_HEIGHT + clearance
self.outer_l = self.inner_l + wall * 2
self.outer_w = self.inner_w + wall * 2
self.outer_h = self.inner_h + wall
def make_bottom(self):
"""创建底壳"""
result = (
cq.Workplane("XY")
.box(self.outer_l, self.outer_w, self.outer_h)
.edges("|Z")
.fillet(self.wall * 1.5)
.faces(">Z")
.shell(-self.wall)
)
# SD卡槽
result = (
result
.faces("<X")
.workplane()
.center(0, -self.outer_h / 2 + self.wall + 2)
.rect(15, 3)
.cutThruAll()
)
# USB口
usb_y = self.outer_w / 2 - self.USB_OFFSET - self.wall
for i in range(2):
result = (
result
.faces(">X")
.workplane()
.center(usb_y - i * 18, -self.outer_h / 2 + self.wall + 8)
.rect(15, 16)
.cutThruAll()
)
# 网口
result = (
result
.faces(">X")
.workplane()
.center(usb_y - 36, -self.outer_h / 2 + self.wall + 8)
.rect(16, 14)
.cutThruAll()
)
# HDMI口
hdmi_y = -self.outer_w / 2 + self.HDMI_OFFSET + self.wall
for i in range(2):
result = (
result
.faces("<Y")
.workplane()
.center(-self.outer_l / 2 + 20 + i * 14, -self.outer_h / 2 + self.wall + 4)
.rect(8, 4)
.cutThruAll()
)
# 电源口
result = (
result
.faces("<Y")
.workplane()
.center(-self.outer_l / 2 + 8, -self.outer_h / 2 + self.wall + 4)
.rect(9, 4)
.cutThruAll()
)
# 散热孔
if self.vent:
result = self._add_vents(result, ">Z")
# 支撑柱
standoff_positions = [
(-self.inner_l/2 + 3.5, -self.inner_w/2 + 3.5),
(-self.inner_l/2 + 3.5, self.inner_w/2 - 3.5),
(-self.inner_l/2 + 3.5 + 58, -self.inner_w/2 + 3.5),
(-self.inner_l/2 + 3.5 + 58, self.inner_w/2 - 3.5),
]
for pos in standoff_positions:
result = (
result
.faces("<Z[1]")
.workplane()
.center(pos[0], pos[1])
.circle(3)
.extrude(3)
.faces(">Z")
.workplane()
.center(pos[0], pos[1])
.hole(2.5, 5)
)
return result
def make_top(self):
"""创建顶盖"""
lid_height = self.wall + 5
result = (
cq.Workplane("XY")
.box(self.outer_l, self.outer_w, lid_height)
.edges("|Z")
.fillet(self.wall * 1.5)
.faces("<Z")
.shell(-self.wall)
)
# 内部凸起(与底壳配合)
result = (
result
.faces("<Z")
.workplane()
.rect(self.inner_l - 1, self.inner_w - 1)
.extrude(-3)
)
# 散热孔
if self.vent:
result = self._add_vents(result, ">Z")
# GPIO开口(可选)
result = (
result
.faces("<X")
.workplane()
.center(self.outer_w / 2 - 15, 0)
.rect(52, 6)
.cutThruAll()
)
return result
def _add_vents(self, model, face_selector):
"""添加散热孔"""
vent_length = 30
vent_width = 2
vent_spacing = 4
num_vents = 8
result = model
for i in range(num_vents):
offset = (i - num_vents / 2 + 0.5) * (vent_width + vent_spacing)
result = (
result
.faces(face_selector)
.workplane()
.center(offset, 0)
.rect(vent_width, vent_length)
.cutBlind(-self.wall)
)
return result
def assemble(self):
"""组装外壳"""
bottom = self.make_bottom()
top = self.make_top()
assy = cq.Assembly(name="raspberry_pi_case")
assy.add(bottom, name="bottom", color=cq.Color("darkgray"))
assy.add(top, name="top", color=cq.Color("darkgray"),
loc=cq.Location(cq.Vector(0, 0, self.outer_h + 2)))
return assy
# 创建外壳
case = RaspberryPiCase(wall=2, clearance=1, vent=True)
bottom = case.make_bottom()
top = case.make_top()
assy = case.assemble()
cq.exporters.export(bottom, "pi_case_bottom.step")
cq.exporters.export(top, "pi_case_top.step")
assy.save("pi_case_assembly.step")
# 同时导出STL用于3D打印
cq.exporters.export(bottom, "pi_case_bottom.stl", tolerance=0.05)
cq.exporters.export(top, "pi_case_top.stl", tolerance=0.05)
print("Raspberry Pi外壳设计完成!")
10.3 3D打印项目
10.3.1 可调节手机支架
import cadquery as cq
import math
class PhoneStand:
"""可调节手机支架"""
def __init__(self, base_width=100, base_depth=80,
max_phone_width=80, max_phone_thickness=12):
self.base_width = base_width
self.base_depth = base_depth
self.max_phone_width = max_phone_width
self.max_phone_thickness = max_phone_thickness
def make_base(self):
"""创建底座"""
result = (
cq.Workplane("XY")
.rect(self.base_width, self.base_depth)
.extrude(5)
.edges("|Z")
.fillet(5)
.edges(">Z")
.fillet(1)
)
# 防滑脚垫凹槽
pad_positions = [
(-self.base_width/2 + 10, -self.base_depth/2 + 10),
(-self.base_width/2 + 10, self.base_depth/2 - 10),
(self.base_width/2 - 10, -self.base_depth/2 + 10),
(self.base_width/2 - 10, self.base_depth/2 - 10),
]
for pos in pad_positions:
result = (
result
.faces("<Z")
.workplane(invert=True)
.center(pos[0], pos[1])
.circle(6)
.cutBlind(-2)
)
return result
def make_back_support(self):
"""创建后支撑"""
angle = 70 # 倾斜角度
height = 120
thickness = 4
# 使用旋转创建倾斜支撑
result = (
cq.Workplane("XZ")
.moveTo(0, 0)
.lineTo(thickness, 0)
.lineTo(thickness, height)
.lineTo(0, height)
.close()
.extrude(self.max_phone_width)
.rotate((0, 0, 0), (0, 1, 0), 90 - angle)
)
# 添加加强筋
result = (
result
.faces("<Y")
.workplane()
.moveTo(-10, 5)
.lineTo(-10, 40)
.lineTo(0, 5)
.close()
.extrude(-thickness)
)
return result
def make_phone_holder(self):
"""创建手机托架"""
holder_width = self.max_phone_width + 20
holder_depth = 30
holder_height = 15
lip_height = 5
result = (
cq.Workplane("XY")
# 主体
.rect(holder_width, holder_depth)
.extrude(holder_height)
# 手机槽
.faces(">Z")
.workplane()
.center(0, -5)
.rect(self.max_phone_width + 2, self.max_phone_thickness + 2)
.cutBlind(-holder_height + 3)
# 前唇
.faces(">Z")
.workplane()
.center(0, holder_depth/2 - 5)
.rect(holder_width - 10, 10)
.extrude(lip_height)
)
# 圆角
result = result.edges("|Z").fillet(3)
# 充电线孔
result = (
result
.faces("<Y")
.workplane()
.center(0, holder_height/2)
.rect(15, 8)
.cutThruAll()
)
return result
def assemble(self):
"""组装支架"""
base = self.make_base()
support = self.make_back_support()
holder = self.make_phone_holder()
assy = cq.Assembly(name="phone_stand")
assy.add(base, name="base", color=cq.Color("lightgray"))
assy.add(support, name="support", color=cq.Color("gray"),
loc=cq.Location(cq.Vector(-self.max_phone_width/2,
-self.base_depth/2 + 10, 5)))
assy.add(holder, name="holder", color=cq.Color("darkgray"),
loc=cq.Location(cq.Vector(0, 10, 5)))
return assy
# 创建手机支架
stand = PhoneStand()
base = stand.make_base()
support = stand.make_back_support()
holder = stand.make_phone_holder()
# 导出
cq.exporters.export(base, "phone_stand_base.stl", tolerance=0.1)
cq.exporters.export(holder, "phone_stand_holder.stl", tolerance=0.1)
print("手机支架设计完成!")
10.4 最佳实践总结
10.4.1 代码组织
"""
推荐的CadQuery项目结构
project/
├── parts/ # 零件定义
│ ├── __init__.py
│ ├── base.py
│ ├── cover.py
│ └── fasteners.py
├── assemblies/ # 装配体定义
│ ├── __init__.py
│ └── main_assembly.py
├── utils/ # 工具函数
│ ├── __init__.py
│ └── helpers.py
├── configs/ # 配置文件
│ └── parameters.json
├── output/ # 输出文件
│ ├── step/
│ └── stl/
├── tests/ # 测试
│ └── test_parts.py
└── main.py # 主程序
"""
# parts/base.py 示例
import cadquery as cq
class BasePart:
"""零件基类"""
def __init__(self, **kwargs):
self.params = kwargs
def build(self):
"""构建零件 - 子类实现"""
raise NotImplementedError
def export(self, filename, format='step'):
"""导出零件"""
part = self.build()
if format.lower() == 'step':
cq.exporters.export(part, f"{filename}.step")
elif format.lower() == 'stl':
cq.exporters.export(part, f"{filename}.stl", tolerance=0.1)
10.4.2 参数管理
import json
class ParameterManager:
"""参数管理器"""
def __init__(self, config_file=None):
self.params = {}
if config_file:
self.load(config_file)
def load(self, filename):
"""从JSON加载参数"""
with open(filename, 'r') as f:
self.params = json.load(f)
def save(self, filename):
"""保存参数到JSON"""
with open(filename, 'w') as f:
json.dump(self.params, f, indent=2)
def get(self, key, default=None):
"""获取参数"""
keys = key.split('.')
value = self.params
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def set(self, key, value):
"""设置参数"""
keys = key.split('.')
d = self.params
for k in keys[:-1]:
if k not in d:
d[k] = {}
d = d[k]
d[keys[-1]] = value
# 使用示例
# config.json:
# {
# "box": {"length": 30, "width": 20, "height": 10},
# "fillet": {"radius": 2}
# }
pm = ParameterManager()
pm.params = {
"box": {"length": 30, "width": 20, "height": 10},
"fillet": {"radius": 2}
}
length = pm.get("box.length", 30)
fillet_r = pm.get("fillet.radius", 2)
10.4.3 错误处理最佳实践
import cadquery as cq
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class CadQueryError(Exception):
"""CadQuery操作错误"""
pass
def safe_fillet(model, radius, selector=None):
"""安全的圆角操作"""
try:
if selector:
edges = model.edges(selector)
else:
edges = model.edges()
# 检查是否有边可以倒角
if len(edges.vals()) == 0:
logger.warning("没有找到可以倒角的边")
return model
# 检查圆角半径是否合理
bb = model.val().BoundingBox()
min_dim = min(bb.xmax - bb.xmin, bb.ymax - bb.ymin, bb.zmax - bb.zmin)
if radius > min_dim / 2:
logger.warning(f"圆角半径 {radius} 可能太大,调整为 {min_dim / 4}")
radius = min_dim / 4
return edges.fillet(radius)
except Exception as e:
logger.error(f"圆角操作失败: {e}")
raise CadQueryError(f"圆角操作失败: {e}")
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
model = safe_fillet(model, 2, "|Z")
10.4.4 文档和注释规范
import cadquery as cq
def make_bracket(
length: float,
width: float,
height: float,
thickness: float = 3.0,
hole_diameter: float = 8.0,
fillet_radius: float = 2.0
) -> cq.Workplane:
"""
创建L型支架
创建一个L型安装支架,包含安装孔和圆角处理。
Args:
length: 支架总长度 (mm)
width: 支架宽度 (mm)
height: 支架高度 (mm)
thickness: 板材厚度 (mm),默认3.0
hole_diameter: 安装孔直径 (mm),默认8.0
fillet_radius: 圆角半径 (mm),默认2.0
Returns:
cq.Workplane: 支架模型
Raises:
ValueError: 如果参数无效
Example:
>>> bracket = make_bracket(50, 30, 40)
>>> cq.exporters.export(bracket, "bracket.step")
Notes:
- 安装孔位于距边缘 hole_diameter 距离处
- 自动对所有竖直边进行圆角处理
"""
# 参数验证
if length <= 0 or width <= 0 or height <= 0:
raise ValueError("尺寸必须为正数")
if thickness >= min(length, width, height) / 2:
raise ValueError("厚度太大")
# 创建L型截面
result = (
cq.Workplane("XZ")
.moveTo(0, 0)
.lineTo(length, 0)
.lineTo(length, thickness)
.lineTo(thickness, thickness)
.lineTo(thickness, height)
.lineTo(0, height)
.close()
.extrude(width)
)
# 添加安装孔
hole_offset = hole_diameter * 1.5
# 底板孔
result = (
result
.faces("<Z")
.workplane(invert=True)
.center(length - hole_offset, width / 2)
.hole(hole_diameter)
)
# 竖板孔
result = (
result
.faces("<X")
.workplane()
.center(width / 2, height - hole_offset)
.hole(hole_diameter)
)
# 圆角
if fillet_radius > 0:
result = result.edges("|Y").fillet(fillet_radius)
return result
10.5 本章小结
本章通过实战案例展示了CadQuery在实际项目中的应用:
-
机械零件设计
- 联轴器设计
- 齿轮箱外壳
-
电子产品外壳
- Raspberry Pi外壳
- 散热和接口设计
-
3D打印项目
- 可调节手机支架
- 打印优化
-
最佳实践
- 代码组织
- 参数管理
- 错误处理
- 文档规范
通过本章的学习,您应该能够:
- 将CadQuery应用于实际项目
- 创建完整的参数化产品设计
- 遵循工程最佳实践
- 组织可维护的代码结构
恭喜您完成了CadQuery完整教程的学习!现在您已经掌握了从基础到高级的CadQuery开发技能,可以开始创建自己的参数化3D模型了。

浙公网安备 33010602011771号