第09章-二次开发进阶

第09章:二次开发进阶

9.1 深入OCP开发

9.1.1 OCCT拓扑结构

理解OpenCASCADE的拓扑结构对于高级开发至关重要:

Compound (复合体)
    └── CompSolid (复合实体)
            └── Solid (实体)
                    └── Shell (壳)
                            └── Face (面)
                                    └── Wire (线框)
                                            └── Edge (边)
                                                    └── Vertex (顶点)
import cadquery as cq
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import (
    TopAbs_COMPOUND, TopAbs_SOLID, TopAbs_SHELL, 
    TopAbs_FACE, TopAbs_WIRE, TopAbs_EDGE, TopAbs_VERTEX
)

def explore_topology(shape):
    """探索OCCT拓扑结构"""
    occ_shape = shape.val().wrapped
    
    topology_types = [
        (TopAbs_COMPOUND, "Compound"),
        (TopAbs_SOLID, "Solid"),
        (TopAbs_SHELL, "Shell"),
        (TopAbs_FACE, "Face"),
        (TopAbs_WIRE, "Wire"),
        (TopAbs_EDGE, "Edge"),
        (TopAbs_VERTEX, "Vertex"),
    ]
    
    print("拓扑结构分析:")
    for topo_type, name in topology_types:
        explorer = TopExp_Explorer(occ_shape, topo_type)
        count = 0
        while explorer.More():
            count += 1
            explorer.Next()
        if count > 0:
            print(f"  {name}: {count}")

# 使用
model = cq.Workplane("XY").box(30, 20, 10)
explore_topology(model)

9.1.2 几何曲线操作

import cadquery as cq
from OCP.Geom import Geom_BSplineCurve
from OCP.TColgp import TColgp_Array1OfPnt
from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire

def make_bspline_edge(control_points, degree=3):
    """
    创建B样条曲线边
    
    Args:
        control_points: 控制点列表 [(x, y, z), ...]
        degree: 曲线阶数
    """
    n = len(control_points)
    
    # 创建控制点数组
    poles = TColgp_Array1OfPnt(1, n)
    for i, (x, y, z) in enumerate(control_points):
        poles.SetValue(i + 1, gp_Pnt(x, y, z))
    
    # 创建节点向量(均匀)
    num_knots = n - degree + 1
    knots = TColStd_Array1OfReal(1, num_knots)
    for i in range(num_knots):
        knots.SetValue(i + 1, float(i))
    
    # 创建重复度数组
    mults = TColStd_Array1OfInteger(1, num_knots)
    for i in range(num_knots):
        if i == 0 or i == num_knots - 1:
            mults.SetValue(i + 1, degree + 1)
        else:
            mults.SetValue(i + 1, 1)
    
    # 创建B样条曲线
    curve = Geom_BSplineCurve(poles, knots, mults, degree)
    
    # 转换为边
    edge_builder = BRepBuilderAPI_MakeEdge(curve)
    edge = edge_builder.Edge()
    
    return cq.Edge(edge)

# 使用
control_pts = [(0, 0, 0), (10, 20, 0), (30, 10, 0), (50, 30, 0), (70, 0, 0)]
edge = make_bspline_edge(control_pts)

9.1.3 曲面操作

import cadquery as cq
from OCP.Geom import Geom_BSplineSurface
from OCP.TColgp import TColgp_Array2OfPnt
from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
import math

def make_bspline_surface(z_func, x_range, y_range, x_points=10, y_points=10):
    """
    从函数创建B样条曲面
    
    Args:
        z_func: z = f(x, y) 函数
        x_range: (x_min, x_max)
        y_range: (y_min, y_max)
        x_points: X方向点数
        y_points: Y方向点数
    """
    # 创建控制点网格
    poles = TColgp_Array2OfPnt(1, x_points, 1, y_points)
    
    x_min, x_max = x_range
    y_min, y_max = y_range
    
    for i in range(x_points):
        for j in range(y_points):
            x = x_min + (x_max - x_min) * i / (x_points - 1)
            y = y_min + (y_max - y_min) * j / (y_points - 1)
            z = z_func(x, y)
            poles.SetValue(i + 1, j + 1, gp_Pnt(x, y, z))
    
    # 创建节点向量
    degree_u = min(3, x_points - 1)
    degree_v = min(3, y_points - 1)
    
    # 简化:使用均匀节点
    u_knots = TColStd_Array1OfReal(1, 2)
    v_knots = TColStd_Array1OfReal(1, 2)
    u_knots.SetValue(1, 0.0)
    u_knots.SetValue(2, 1.0)
    v_knots.SetValue(1, 0.0)
    v_knots.SetValue(2, 1.0)
    
    u_mults = TColStd_Array1OfInteger(1, 2)
    v_mults = TColStd_Array1OfInteger(1, 2)
    u_mults.SetValue(1, x_points)
    u_mults.SetValue(2, x_points)
    v_mults.SetValue(1, y_points)
    v_mults.SetValue(2, y_points)
    
    # 创建B样条曲面
    surface = Geom_BSplineSurface(
        poles, u_knots, v_knots, u_mults, v_mults,
        degree_u, degree_v
    )
    
    # 转换为面
    face_builder = BRepBuilderAPI_MakeFace(surface, 1e-6)
    face = face_builder.Face()
    
    return cq.Face(face)

# 使用示例
def wave_function(x, y):
    return 10 * math.sin(x * 0.2) * math.cos(y * 0.2)

# face = make_bspline_surface(wave_function, (-20, 20), (-20, 20))

9.1.4 高级布尔运算

import cadquery as cq
from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse, BRepAlgoAPI_Cut, BRepAlgoAPI_Common
from OCP.BRepAlgoAPI import BRepAlgoAPI_Section

def advanced_boolean(shape1, shape2, operation="fuse"):
    """
    高级布尔运算,提供更多控制
    
    Args:
        shape1, shape2: CadQuery形状
        operation: "fuse", "cut", "common", "section"
    """
    s1 = shape1.val().wrapped
    s2 = shape2.val().wrapped
    
    if operation == "fuse":
        builder = BRepAlgoAPI_Fuse(s1, s2)
    elif operation == "cut":
        builder = BRepAlgoAPI_Cut(s1, s2)
    elif operation == "common":
        builder = BRepAlgoAPI_Common(s1, s2)
    elif operation == "section":
        builder = BRepAlgoAPI_Section(s1, s2)
    else:
        raise ValueError(f"未知操作: {operation}")
    
    # 执行运算
    builder.Build()
    
    if not builder.IsDone():
        raise RuntimeError("布尔运算失败")
    
    result_shape = builder.Shape()
    return cq.Workplane("XY").add(cq.Shape.cast(result_shape))

# 使用
box = cq.Workplane("XY").box(30, 30, 30)
sphere = cq.Workplane("XY").sphere(20)

fused = advanced_boolean(box, sphere, "fuse")
cut = advanced_boolean(box, sphere, "cut")
common = advanced_boolean(box, sphere, "common")

9.2 自定义建模函数库

9.2.1 标准件库

import cadquery as cq
import math

class StandardParts:
    """标准件库"""
    
    @staticmethod
    def hex_bolt(diameter, length, head_height=None, thread_length=None):
        """
        六角螺栓
        
        Args:
            diameter: 公称直径(如M8对应8)
            length: 螺栓总长度
            head_height: 头部高度(默认为0.7*diameter)
            thread_length: 螺纹长度(默认为全螺纹)
        """
        if head_height is None:
            head_height = diameter * 0.7
        if thread_length is None:
            thread_length = length
        
        head_width = diameter * 1.7  # 对边距离
        
        return (
            cq.Workplane("XY")
            .polygon(6, head_width * 2 / math.sqrt(3))  # 六边形
            .extrude(head_height)
            .faces("<Z")
            .workplane()
            .circle(diameter / 2)
            .extrude(-length)
            .edges(">Z")
            .chamfer(head_height * 0.1)
            .edges("<Z")
            .chamfer(diameter * 0.1)
        )
    
    @staticmethod
    def hex_nut(diameter, height=None):
        """
        六角螺母
        
        Args:
            diameter: 公称直径
            height: 高度(默认为0.8*diameter)
        """
        if height is None:
            height = diameter * 0.8
        
        width = diameter * 1.7
        
        return (
            cq.Workplane("XY")
            .polygon(6, width * 2 / math.sqrt(3))
            .extrude(height)
            .faces(">Z")
            .workplane()
            .hole(diameter)
            .edges()
            .chamfer(height * 0.1)
        )
    
    @staticmethod
    def washer(inner_diameter, outer_diameter, thickness):
        """
        垫圈
        
        Args:
            inner_diameter: 内径
            outer_diameter: 外径
            thickness: 厚度
        """
        return (
            cq.Workplane("XY")
            .circle(outer_diameter / 2)
            .circle(inner_diameter / 2)
            .extrude(thickness)
        )
    
    @staticmethod
    def bearing(inner_d, outer_d, width):
        """
        简化轴承
        
        Args:
            inner_d: 内径
            outer_d: 外径
            width: 宽度
        """
        return (
            cq.Workplane("XY")
            .circle(outer_d / 2)
            .circle(inner_d / 2)
            .extrude(width)
            .faces(">Z or <Z")
            .shell(-width * 0.1)
        )

# 使用
bolt = StandardParts.hex_bolt(8, 30)
nut = StandardParts.hex_nut(8)
washer = StandardParts.washer(8.4, 16, 1.5)

cq.exporters.export(bolt, "m8_bolt.step")
cq.exporters.export(nut, "m8_nut.step")
cq.exporters.export(washer, "washer.step")

9.2.2 机械零件库

import cadquery as cq
import math

class MechanicalParts:
    """机械零件库"""
    
    @staticmethod
    def spur_gear(module, teeth, width, bore, pressure_angle=20):
        """
        简化直齿轮
        
        Args:
            module: 模数
            teeth: 齿数
            width: 齿宽
            bore: 轴孔直径
            pressure_angle: 压力角
        """
        pitch_radius = module * teeth / 2
        addendum = module
        dedendum = 1.25 * module
        
        outer_radius = pitch_radius + addendum
        root_radius = pitch_radius - dedendum
        
        # 简化为圆柱体(实际齿轮需要更复杂的几何)
        return (
            cq.Workplane("XY")
            .circle(outer_radius)
            .extrude(width)
            .faces(">Z")
            .workplane()
            .hole(bore)
            # 添加轮辐(可选)
            .faces(">Z or <Z")
            .shell(-width * 0.2)
        )
    
    @staticmethod
    def shaft(diameter, length, keyway=None, keyway_depth=None):
        """
        轴
        
        Args:
            diameter: 轴径
            length: 长度
            keyway: 键槽宽度(可选)
            keyway_depth: 键槽深度(可选)
        """
        result = cq.Workplane("XY").cylinder(length, diameter / 2)
        
        if keyway and keyway_depth:
            result = (
                result
                .faces(">Z")
                .workplane()
                .transformed(offset=(diameter/2 - keyway_depth/2, 0, 0))
                .rect(keyway_depth, keyway)
                .cutBlind(-length * 0.6)
            )
        
        # 倒角
        result = result.faces(">Z or <Z").edges().chamfer(diameter * 0.05)
        
        return result
    
    @staticmethod
    def pulley(outer_diameter, bore, width, groove_depth=5, num_grooves=1):
        """
        皮带轮
        
        Args:
            outer_diameter: 外径
            bore: 轴孔直径
            width: 宽度
            groove_depth: 槽深
            num_grooves: 槽数
        """
        result = (
            cq.Workplane("XY")
            .circle(outer_diameter / 2)
            .extrude(width)
            .faces(">Z")
            .workplane()
            .hole(bore)
        )
        
        # 添加V型槽
        groove_width = width / (num_grooves + 1)
        for i in range(num_grooves):
            offset = groove_width * (i + 1) - width / 2
            result = (
                result
                .faces(">Z")
                .workplane(offset=offset)
                .transformed(rotate=(90, 0, 0))
                .circle(outer_diameter / 2 - groove_depth)
                .extrude(groove_width * 0.3, both=True)
            )
        
        return result
    
    @staticmethod
    def flange(outer_d, inner_d, thickness, bolt_circle_d, 
               bolt_hole_d, num_bolts, hub_d=None, hub_h=0):
        """
        法兰盘
        
        Args:
            outer_d: 外径
            inner_d: 内径
            thickness: 厚度
            bolt_circle_d: 螺栓圆直径
            bolt_hole_d: 螺栓孔直径
            num_bolts: 螺栓数量
            hub_d: 凸台直径
            hub_h: 凸台高度
        """
        result = (
            cq.Workplane("XY")
            .circle(outer_d / 2)
            .extrude(thickness)
        )
        
        # 凸台
        if hub_d and hub_h > 0:
            result = (
                result
                .faces(">Z")
                .workplane()
                .circle(hub_d / 2)
                .extrude(hub_h)
            )
        
        # 中心孔
        result = result.faces(">Z").workplane().hole(inner_d)
        
        # 螺栓孔
        result = (
            result
            .faces("<Z")
            .workplane(invert=True)
            .polarArray(bolt_circle_d / 2, 0, 360, num_bolts)
            .hole(bolt_hole_d)
        )
        
        return result

# 使用
gear = MechanicalParts.spur_gear(2, 30, 15, 10)
shaft = MechanicalParts.shaft(20, 100, keyway=6, keyway_depth=3)
flange = MechanicalParts.flange(100, 50, 15, 75, 10, 6, hub_d=60, hub_h=20)

cq.exporters.export(gear, "gear.step")
cq.exporters.export(shaft, "shaft.step")
cq.exporters.export(flange, "flange.step")

9.2.3 电子外壳库

import cadquery as cq

class ElectronicEnclosures:
    """电子外壳库"""
    
    @staticmethod
    def simple_box(length, width, height, wall_thickness=2, corner_radius=3):
        """简单盒子外壳"""
        return (
            cq.Workplane("XY")
            .box(length, width, height)
            .edges("|Z")
            .fillet(corner_radius)
            .faces(">Z")
            .shell(-wall_thickness)
        )
    
    @staticmethod
    def pcb_enclosure(pcb_length, pcb_width, pcb_height,
                      wall=2, clearance=1, standoff_height=5,
                      standoff_diameter=6, hole_diameter=2.5):
        """
        PCB外壳
        
        Args:
            pcb_length, pcb_width: PCB尺寸
            pcb_height: PCB上最高元件高度
            wall: 壁厚
            clearance: PCB周边间隙
            standoff_height: 支撑柱高度
            standoff_diameter: 支撑柱直径
            hole_diameter: 安装孔直径
        """
        inner_l = pcb_length + clearance * 2
        inner_w = pcb_width + clearance * 2
        inner_h = standoff_height + pcb_height + clearance
        
        outer_l = inner_l + wall * 2
        outer_w = inner_w + wall * 2
        outer_h = inner_h + wall
        
        # 创建底壳
        base = (
            cq.Workplane("XY")
            .box(outer_l, outer_w, outer_h)
            .edges("|Z")
            .fillet(wall)
            .faces(">Z")
            .shell(-wall)
        )
        
        # 添加支撑柱
        pcb_offset_x = (pcb_length - standoff_diameter) / 2
        pcb_offset_y = (pcb_width - standoff_diameter) / 2
        
        standoff_positions = [
            (-pcb_offset_x, -pcb_offset_y),
            (-pcb_offset_x, pcb_offset_y),
            (pcb_offset_x, -pcb_offset_y),
            (pcb_offset_x, pcb_offset_y),
        ]
        
        base = (
            base
            .faces("<Z[1]")  # 内底面
            .workplane()
            .pushPoints(standoff_positions)
            .circle(standoff_diameter / 2)
            .extrude(standoff_height)
            .faces(">Z")
            .workplane()
            .pushPoints(standoff_positions)
            .hole(hole_diameter, standoff_height)
        )
        
        return base
    
    @staticmethod
    def vented_enclosure(length, width, height, wall=2, 
                         vent_width=2, vent_spacing=4, vent_length=20):
        """
        带散热孔的外壳
        """
        # 基础外壳
        result = (
            cq.Workplane("XY")
            .box(length, width, height)
            .edges("|Z")
            .fillet(wall * 1.5)
            .faces(">Z")
            .shell(-wall)
        )
        
        # 在侧面添加散热孔
        num_vents = int((height - 10) / (vent_width + vent_spacing))
        
        for i in range(num_vents):
            z_offset = 5 + i * (vent_width + vent_spacing)
            result = (
                result
                .faces(">X")
                .workplane()
                .center(0, z_offset - height/2 + wall)
                .rect(vent_length, vent_width)
                .cutBlind(-wall)
            )
        
        return result

# 使用
simple_box = ElectronicEnclosures.simple_box(100, 60, 30)
pcb_case = ElectronicEnclosures.pcb_enclosure(80, 50, 20)
vented = ElectronicEnclosures.vented_enclosure(120, 80, 50)

cq.exporters.export(pcb_case, "pcb_enclosure.step")

9.3 Web服务集成

9.3.1 Flask REST API

import cadquery as cq
from flask import Flask, request, send_file, jsonify
import io
import tempfile
import os

app = Flask(__name__)

@app.route('/api/generate_box', methods=['POST'])
def generate_box():
    """生成盒子并返回STEP文件"""
    data = request.json
    
    length = data.get('length', 30)
    width = data.get('width', 20)
    height = data.get('height', 10)
    fillet = data.get('fillet', 0)
    
    # 创建模型
    result = cq.Workplane("XY").box(length, width, height)
    
    if fillet > 0:
        result = result.edges().fillet(fillet)
    
    # 导出到临时文件
    with tempfile.NamedTemporaryFile(suffix='.step', delete=False) as f:
        cq.exporters.export(result, f.name)
        return send_file(f.name, as_attachment=True, 
                        download_name='box.step')

@app.route('/api/model_info', methods=['POST'])
def model_info():
    """返回模型信息"""
    data = request.json
    
    length = data.get('length', 30)
    width = data.get('width', 20)
    height = data.get('height', 10)
    
    result = cq.Workplane("XY").box(length, width, height)
    bb = result.val().BoundingBox()
    
    return jsonify({
        'volume': result.val().Volume(),
        'bounding_box': {
            'min': [bb.xmin, bb.ymin, bb.zmin],
            'max': [bb.xmax, bb.ymax, bb.zmax]
        },
        'faces_count': len(result.faces().vals()),
        'edges_count': len(result.edges().vals())
    })

# if __name__ == '__main__':
#     app.run(debug=True)

9.3.2 FastAPI集成

import cadquery as cq
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel
import tempfile
import os

app = FastAPI(title="CadQuery API")

class BoxParams(BaseModel):
    length: float = 30
    width: float = 20
    height: float = 10
    fillet_radius: float = 0

class CylinderParams(BaseModel):
    radius: float = 10
    height: float = 30
    hollow: bool = False
    wall_thickness: float = 2

@app.post("/generate/box")
async def generate_box(params: BoxParams):
    """生成参数化盒子"""
    try:
        result = cq.Workplane("XY").box(params.length, params.width, params.height)
        
        if params.fillet_radius > 0:
            max_fillet = min(params.length, params.width, params.height) / 2 * 0.9
            fillet = min(params.fillet_radius, max_fillet)
            result = result.edges().fillet(fillet)
        
        # 导出
        with tempfile.NamedTemporaryFile(suffix='.step', delete=False) as f:
            cq.exporters.export(result, f.name)
            return FileResponse(f.name, filename='box.step',
                              media_type='application/octet-stream')
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/generate/cylinder")
async def generate_cylinder(params: CylinderParams):
    """生成参数化圆柱"""
    try:
        result = cq.Workplane("XY").cylinder(params.height, params.radius)
        
        if params.hollow:
            inner_radius = params.radius - params.wall_thickness
            if inner_radius > 0:
                result = result.faces(">Z").workplane().hole(inner_radius * 2)
        
        with tempfile.NamedTemporaryFile(suffix='.step', delete=False) as f:
            cq.exporters.export(result, f.name)
            return FileResponse(f.name, filename='cylinder.step',
                              media_type='application/octet-stream')
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# 运行: uvicorn main:app --reload

9.3.3 批量处理服务

import cadquery as cq
import json
import os
from concurrent.futures import ProcessPoolExecutor

def generate_part(config):
    """生成单个零件"""
    part_type = config.get('type')
    params = config.get('params', {})
    output = config.get('output', 'part.step')
    
    if part_type == 'box':
        result = cq.Workplane("XY").box(
            params.get('length', 30),
            params.get('width', 20),
            params.get('height', 10)
        )
    elif part_type == 'cylinder':
        result = cq.Workplane("XY").cylinder(
            params.get('height', 30),
            params.get('radius', 10)
        )
    elif part_type == 'sphere':
        result = cq.Workplane("XY").sphere(
            params.get('radius', 15)
        )
    else:
        raise ValueError(f"未知类型: {part_type}")
    
    cq.exporters.export(result, output)
    return output

def batch_generate(config_file, output_dir='output'):
    """批量生成零件"""
    os.makedirs(output_dir, exist_ok=True)
    
    with open(config_file, 'r') as f:
        configs = json.load(f)
    
    results = []
    for i, config in enumerate(configs):
        config['output'] = os.path.join(output_dir, f"part_{i}.step")
        result = generate_part(config)
        results.append(result)
        print(f"生成: {result}")
    
    return results

# 配置文件示例 (batch_config.json):
# [
#     {"type": "box", "params": {"length": 30, "width": 20, "height": 10}},
#     {"type": "cylinder", "params": {"radius": 15, "height": 40}},
#     {"type": "sphere", "params": {"radius": 20}}
# ]

9.4 与其他工具集成

9.4.1 FreeCAD集成

# 在FreeCAD中使用CadQuery
# 需要安装cadquery-freecad-module

import cadquery as cq

def export_for_freecad(model, filename):
    """导出为FreeCAD可读格式"""
    cq.exporters.export(model, filename + ".step")
    cq.exporters.export(model, filename + ".brep")

# 示例
model = cq.Workplane("XY").box(30, 20, 10)
export_for_freecad(model, "for_freecad")

9.4.2 Blender集成

import cadquery as cq

def export_for_blender(model, filename, format='stl'):
    """
    导出为Blender可导入的格式
    
    Args:
        model: CadQuery模型
        filename: 输出文件名
        format: 'stl' 或 'gltf'
    """
    if format == 'stl':
        cq.exporters.export(model, filename + ".stl", tolerance=0.01)
    elif format == 'gltf':
        cq.exporters.export(model, filename + ".gltf")
    else:
        raise ValueError(f"不支持的格式: {format}")

# 使用
model = cq.Workplane("XY").box(30, 20, 10)
export_for_blender(model, "for_blender", format='gltf')

9.4.3 3D打印切片软件集成

import cadquery as cq
import subprocess
import os

def prepare_for_printing(model, filename, layer_height=0.2, 
                         infill=20, slicer='cura'):
    """
    准备3D打印文件
    
    Args:
        model: CadQuery模型
        filename: 输出文件名
        layer_height: 层高
        infill: 填充率
        slicer: 切片软件 ('cura', 'prusa')
    """
    # 导出优化的STL
    stl_file = filename + ".stl"
    tolerance = layer_height / 2
    
    cq.exporters.export(model, stl_file, tolerance=tolerance)
    
    print(f"STL文件已生成: {stl_file}")
    print(f"建议打印参数:")
    print(f"  层高: {layer_height}mm")
    print(f"  填充: {infill}%")
    
    # 可选:调用切片软件
    # if slicer == 'cura':
    #     subprocess.run(['cura', stl_file])

# 使用
model = cq.Workplane("XY").box(30, 20, 10)
prepare_for_printing(model, "print_ready", layer_height=0.2, infill=20)

9.5 性能优化高级技巧

9.5.1 并行处理

import cadquery as cq
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import multiprocessing

def create_part(params):
    """创建单个零件(独立函数,可序列化)"""
    name, length, width, height = params
    result = cq.Workplane("XY").box(length, width, height)
    cq.exporters.export(result, f"{name}.step")
    return name

def parallel_generation(parts_params, max_workers=None):
    """
    并行生成多个零件
    
    Args:
        parts_params: [(name, l, w, h), ...]
        max_workers: 最大工作进程数
    """
    if max_workers is None:
        max_workers = multiprocessing.cpu_count()
    
    with ProcessPoolExecutor(max_workers=max_workers) as executor:
        results = list(executor.map(create_part, parts_params))
    
    return results

# 使用
parts = [
    ("part_1", 30, 20, 10),
    ("part_2", 40, 30, 15),
    ("part_3", 50, 40, 20),
    ("part_4", 60, 50, 25),
]

# results = parallel_generation(parts)

9.5.2 内存优化

import cadquery as cq
import gc

def memory_efficient_batch(configs, batch_size=10):
    """
    内存高效的批量处理
    
    Args:
        configs: 配置列表
        batch_size: 批次大小
    """
    for i in range(0, len(configs), batch_size):
        batch = configs[i:i + batch_size]
        
        for config in batch:
            # 处理
            model = cq.Workplane("XY").box(
                config['length'],
                config['width'],
                config['height']
            )
            cq.exporters.export(model, config['output'])
            
            # 清理
            del model
        
        # 批次结束后强制垃圾回收
        gc.collect()
        print(f"完成批次 {i // batch_size + 1}")

9.6 本章小结

本章介绍了CadQuery二次开发的进阶内容:

  1. 深入OCP开发

    • OCCT拓扑结构
    • 曲线曲面操作
    • 高级布尔运算
  2. 自定义建模函数库

    • 标准件库
    • 机械零件库
    • 电子外壳库
  3. Web服务集成

    • Flask/FastAPI API
    • 批量处理服务
  4. 工具集成

    • FreeCAD集成
    • Blender集成
    • 3D打印工作流
  5. 性能优化

    • 并行处理
    • 内存优化

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

  • 使用OCP进行底层几何操作
  • 创建可复用的零件库
  • 构建Web服务
  • 集成到现有工作流程
  • 优化大规模处理性能

下一章将介绍实战案例,展示CadQuery在实际项目中的应用。


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