第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在实际项目中的应用:

  1. 机械零件设计

    • 联轴器设计
    • 齿轮箱外壳
  2. 电子产品外壳

    • Raspberry Pi外壳
    • 散热和接口设计
  3. 3D打印项目

    • 可调节手机支架
    • 打印优化
  4. 最佳实践

    • 代码组织
    • 参数管理
    • 错误处理
    • 文档规范

通过本章的学习,您应该能够:

  • 将CadQuery应用于实际项目
  • 创建完整的参数化产品设计
  • 遵循工程最佳实践
  • 组织可维护的代码结构

恭喜您完成了CadQuery完整教程的学习!现在您已经掌握了从基础到高级的CadQuery开发技能,可以开始创建自己的参数化3D模型了。


posted @ 2026-01-10 13:16  我才是银古  阅读(43)  评论(0)    收藏  举报