第08章-二次开发基础
第08章:二次开发基础
8.1 CadQuery架构深入
8.1.1 CadQuery代码结构
CadQuery的代码结构清晰,主要包含以下模块:
cadquery/
├── __init__.py # 主入口,导出公共API
├── cq.py # 核心Workplane类
├── selectors.py # 选择器系统
├── exporters/ # 导出模块
│ ├── __init__.py
│ ├── assembly.py
│ └── utils.py
├── importers/ # 导入模块
│ ├── __init__.py
│ └── ...
├── occ_impl/ # OCCT包装层
│ ├── shapes.py # 形状类
│ ├── geom.py # 几何类
│ └── ...
├── sketch.py # Sketch API
└── assembly.py # 装配体模块
8.1.2 核心类关系
# CadQuery核心类层次
# 1. Workplane - 主要的用户接口
class Workplane:
"""工作平面类,链式调用的核心"""
plane: Plane # 当前工作平面
objects: List[Shape] # 对象栈
ctx: BuildContext # 构建上下文
# 2. Shape - 几何形状基类
class Shape:
"""所有几何形状的基类"""
wrapped: TopoDS_Shape # OCCT底层对象
# 3. Compound, Solid, Face, Edge, Vertex - 具体形状类
class Solid(Shape):
"""实体形状"""
pass
# 4. Selector - 选择器基类
class Selector:
"""选择器基类"""
def filter(self, objectList): ...
8.1.3 OCP绑定层
CadQuery使用OCP(Open CASCADE Python)作为与OCCT内核的桥梁:
# OCP提供的主要模块
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse, BRepAlgoAPI_Cut
from OCP.gp import gp_Pnt, gp_Vec, gp_Ax1
# 示例:直接使用OCP创建盒子
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
box_maker = BRepPrimAPI_MakeBox(10, 20, 30)
box_shape = box_maker.Shape()
8.2 扩展CadQuery
8.2.1 添加自定义方法
CadQuery允许通过猴子补丁方式添加自定义方法:
import cadquery as cq
def custom_rounded_box(self, length, width, height, radius):
"""
创建圆角盒子的自定义方法
Args:
length: 长度
width: 宽度
height: 高度
radius: 圆角半径
"""
return (
self
.rect(length, width)
.extrude(height)
.edges("|Z")
.fillet(radius)
)
# 将方法添加到Workplane类
cq.Workplane.roundedBox = custom_rounded_box
# 使用自定义方法
result = cq.Workplane("XY").roundedBox(30, 20, 10, 3)
cq.exporters.export(result, "rounded_box.step")
8.2.2 创建自定义选择器
import cadquery as cq
from cadquery import Selector
class AreaSelector(Selector):
"""按面积选择面"""
def __init__(self, min_area=0, max_area=float('inf')):
self.min_area = min_area
self.max_area = max_area
def filter(self, objectList):
result = []
for obj in objectList:
if hasattr(obj, 'Area'):
area = obj.Area()
if self.min_area <= area <= self.max_area:
result.append(obj)
return result
# 使用自定义选择器
model = cq.Workplane("XY").box(50, 30, 20)
large_faces = model.faces(AreaSelector(min_area=1000))
print(f"大面积面数: {len(large_faces.vals())}")
8.2.3 扩展导出格式
import cadquery as cq
import json
def export_to_custom_format(shape, filename):
"""导出为自定义JSON格式"""
bb = shape.val().BoundingBox()
vol = shape.val().Volume()
data = {
"type": "CadQueryModel",
"version": "1.0",
"bounding_box": {
"min": [bb.xmin, bb.ymin, bb.zmin],
"max": [bb.xmax, bb.ymax, bb.zmax]
},
"volume": vol,
"faces_count": len(shape.faces().vals()),
"edges_count": len(shape.edges().vals())
}
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
# 添加到exporters模块(可选)
# cq.exporters.export_custom = export_to_custom_format
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
export_to_custom_format(model, "model_info.json")
8.3 使用OCP直接操作
8.3.1 OCP基础操作
import cadquery as cq
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2
# 使用OCP创建盒子
def make_box_ocp(length, width, height):
"""使用OCP直接创建盒子"""
builder = BRepPrimAPI_MakeBox(length, width, height)
shape = builder.Shape()
# 转换为CadQuery Shape对象
return cq.Shape.cast(shape)
# 使用OCP创建圆柱
def make_cylinder_ocp(radius, height):
"""使用OCP直接创建圆柱"""
builder = BRepPrimAPI_MakeCylinder(radius, height)
shape = builder.Shape()
return cq.Shape.cast(shape)
# 测试
box = make_box_ocp(30, 20, 10)
print(f"体积: {box.Volume()}")
8.3.2 复杂几何操作
import cadquery as cq
from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_EDGE
def advanced_fillet(shape, radius):
"""使用OCP实现高级圆角"""
# 获取底层OCCT形状
occ_shape = shape.val().wrapped
# 创建圆角构造器
fillet_builder = BRepFilletAPI_MakeFillet(occ_shape)
# 遍历所有边并添加圆角
explorer = TopExp_Explorer(occ_shape, TopAbs_EDGE)
while explorer.More():
edge = explorer.Current()
fillet_builder.Add(radius, edge)
explorer.Next()
# 构建结果
result_shape = fillet_builder.Shape()
# 转换回CadQuery
return cq.Workplane("XY").add(cq.Shape.cast(result_shape))
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
filleted = advanced_fillet(model, 2)
cq.exporters.export(filleted, "advanced_fillet.step")
8.3.3 访问底层几何
import cadquery as cq
from OCP.BRep import BRep_Tool
from OCP.TopoDS import TopoDS
from OCP.Geom import Geom_Surface
def analyze_faces(model):
"""分析模型的面信息"""
faces = model.faces().vals()
for i, face in enumerate(faces):
# 获取底层OCCT面
occ_face = face.wrapped
# 获取几何曲面
surface = BRep_Tool.Surface_s(occ_face)
# 获取面积
area = face.Area()
# 获取中心点
center = face.Center()
print(f"面 {i+1}:")
print(f" 面积: {area:.2f} mm²")
print(f" 中心: ({center.x:.2f}, {center.y:.2f}, {center.z:.2f})")
print(f" 类型: {type(surface).__name__}")
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
analyze_faces(model)
8.4 创建CadQuery插件
8.4.1 插件结构
标准CadQuery插件结构:
my_cq_plugin/
├── __init__.py
├── operations.py
├── selectors.py
└── utils.py
8.4.2 基本插件示例
# my_cq_plugin/__init__.py
"""
我的CadQuery插件
提供额外的建模功能
"""
import cadquery as cq
from .operations import *
from .selectors import *
__version__ = "0.1.0"
def register_all():
"""注册所有扩展"""
register_operations()
register_selectors()
# my_cq_plugin/operations.py
"""自定义操作"""
import cadquery as cq
def hexagonal_pattern(self, radius, count, feature_func):
"""
创建六边形图案
Args:
radius: 图案半径
count: 每圈数量
feature_func: 在每个位置执行的函数
"""
import math
result = self
for i in range(count):
angle = 2 * math.pi * i / count
x = radius * math.cos(angle)
y = radius * math.sin(angle)
result = result.center(x, y)
result = feature_func(result)
result = result.center(-x, -y)
return result
def register_operations():
"""注册自定义操作到Workplane"""
cq.Workplane.hexPattern = hexagonal_pattern
# my_cq_plugin/selectors.py
"""自定义选择器"""
import cadquery as cq
from cadquery import Selector
class TypeSelector(Selector):
"""按几何类型选择"""
def __init__(self, geom_type):
self.geom_type = geom_type
def filter(self, objectList):
return [obj for obj in objectList
if self.geom_type.lower() in type(obj).__name__.lower()]
def register_selectors():
"""注册选择器(如果需要)"""
pass
8.4.3 使用插件
import cadquery as cq
import my_cq_plugin
# 注册插件功能
my_cq_plugin.register_all()
# 使用插件提供的功能
result = (
cq.Workplane("XY")
.box(100, 100, 10)
.faces(">Z")
.workplane()
.hexPattern(30, 6, lambda wp: wp.circle(5).cutBlind(-3))
)
8.5 集成外部工具
8.5.1 与NumPy集成
import cadquery as cq
import numpy as np
def make_surface_from_array(z_array, x_size, y_size, z_scale=1.0):
"""
从NumPy数组创建曲面
Args:
z_array: 2D NumPy数组,包含Z值
x_size: X方向尺寸
y_size: Y方向尺寸
z_scale: Z方向缩放
"""
rows, cols = z_array.shape
x_step = x_size / (cols - 1)
y_step = y_size / (rows - 1)
# 生成点列表
points = []
for i in range(rows):
row_points = []
for j in range(cols):
x = j * x_step - x_size / 2
y = i * y_step - y_size / 2
z = z_array[i, j] * z_scale
row_points.append((x, y, z))
points.append(row_points)
return points
# 创建一个正弦波表面
x = np.linspace(0, 2*np.pi, 20)
y = np.linspace(0, 2*np.pi, 20)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
surface_points = make_surface_from_array(Z, 50, 50, z_scale=10)
print(f"生成了 {len(surface_points)} x {len(surface_points[0])} 个点")
8.5.2 与Pandas集成
import cadquery as cq
import pandas as pd
def generate_parts_from_csv(csv_file):
"""从CSV文件批量生成零件"""
df = pd.read_csv(csv_file)
parts = {}
for _, row in df.iterrows():
name = row['name']
length = row['length']
width = row['width']
height = row['height']
part = (
cq.Workplane("XY")
.box(length, width, height)
.edges()
.fillet(min(length, width, height) * 0.05)
)
parts[name] = part
cq.exporters.export(part, f"{name}.step")
return parts
# CSV文件格式:
# name,length,width,height
# part_a,30,20,10
# part_b,50,30,15
# part_c,40,40,20
8.5.3 与matplotlib集成
import cadquery as cq
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
def visualize_model(model, filename="model_preview.png"):
"""使用matplotlib可视化模型"""
# 获取模型的三角网格
# 这需要先导出为STL然后解析,或使用其他方法
# 简化:只绘制边界框
bb = model.val().BoundingBox()
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制边界框
vertices = [
[bb.xmin, bb.ymin, bb.zmin],
[bb.xmax, bb.ymin, bb.zmin],
[bb.xmax, bb.ymax, bb.zmin],
[bb.xmin, bb.ymax, bb.zmin],
[bb.xmin, bb.ymin, bb.zmax],
[bb.xmax, bb.ymin, bb.zmax],
[bb.xmax, bb.ymax, bb.zmax],
[bb.xmin, bb.ymax, bb.zmax]
]
# 定义边界框的12条边
edges = [
[0, 1], [1, 2], [2, 3], [3, 0], # 底面
[4, 5], [5, 6], [6, 7], [7, 4], # 顶面
[0, 4], [1, 5], [2, 6], [3, 7] # 竖直边
]
for edge in edges:
points = [vertices[edge[0]], vertices[edge[1]]]
ax.plot3D(*zip(*points), 'b-')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('模型预览')
plt.savefig(filename)
plt.close()
print(f"预览图已保存: {filename}")
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
visualize_model(model)
8.6 错误处理与调试
8.6.1 常见错误类型
import cadquery as cq
# 1. 几何构建错误
try:
# 错误:圆角半径太大
result = cq.Workplane("XY").box(10, 10, 10).edges().fillet(10)
except Exception as e:
print(f"几何错误: {e}")
# 2. 选择器错误
try:
# 错误:无效的选择器语法
result = cq.Workplane("XY").box(10, 10, 10).faces(">>Z")
except Exception as e:
print(f"选择器错误: {e}")
# 3. 布尔运算错误
try:
# 错误:不相交的布尔运算
box1 = cq.Workplane("XY").box(10, 10, 10)
box2 = cq.Workplane("XY").center(100, 100).box(5, 5, 5)
result = box1.intersect(box2) # 交集为空
except Exception as e:
print(f"布尔运算错误: {e}")
8.6.2 调试装饰器
import cadquery as cq
import functools
import traceback
def debug_operation(func):
"""调试装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用: {func.__name__}")
print(f" 参数: {args[1:]}") # 跳过self
print(f" 关键字参数: {kwargs}")
try:
result = func(*args, **kwargs)
print(f" 成功!")
return result
except Exception as e:
print(f" 失败: {e}")
traceback.print_exc()
raise
return wrapper
# 使用装饰器
class DebugWorkplane(cq.Workplane):
@debug_operation
def box(self, *args, **kwargs):
return super().box(*args, **kwargs)
@debug_operation
def hole(self, *args, **kwargs):
return super().hole(*args, **kwargs)
8.6.3 日志记录
import cadquery as cq
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='cadquery_debug.log'
)
logger = logging.getLogger('CadQueryDebug')
def logged_export(model, filename):
"""带日志的导出函数"""
logger.info(f"开始导出: {filename}")
try:
# 记录模型信息
vol = model.val().Volume()
faces = len(model.faces().vals())
logger.debug(f"模型体积: {vol:.2f}")
logger.debug(f"面数: {faces}")
# 执行导出
cq.exporters.export(model, filename)
logger.info(f"导出成功: {filename}")
except Exception as e:
logger.error(f"导出失败: {e}")
raise
# 使用
model = cq.Workplane("XY").box(30, 20, 10)
logged_export(model, "logged_model.step")
8.7 测试框架
8.7.1 单元测试基础
import unittest
import cadquery as cq
class TestCadQueryOperations(unittest.TestCase):
"""CadQuery操作测试"""
def test_box_creation(self):
"""测试盒子创建"""
box = cq.Workplane("XY").box(30, 20, 10)
vol = box.val().Volume()
self.assertAlmostEqual(vol, 30 * 20 * 10, places=1)
def test_cylinder_creation(self):
"""测试圆柱创建"""
import math
cyl = cq.Workplane("XY").cylinder(20, 10)
vol = cyl.val().Volume()
expected = math.pi * 10**2 * 20
self.assertAlmostEqual(vol, expected, places=0)
def test_hole_creation(self):
"""测试孔创建"""
result = (
cq.Workplane("XY")
.box(30, 30, 10)
.faces(">Z")
.workplane()
.hole(10)
)
# 验证体积减少
box_vol = 30 * 30 * 10
result_vol = result.val().Volume()
self.assertLess(result_vol, box_vol)
def test_fillet_edges(self):
"""测试圆角"""
result = (
cq.Workplane("XY")
.box(30, 20, 10)
.edges("|Z")
.fillet(2)
)
# 验证模型有效
self.assertIsNotNone(result.val())
if __name__ == '__main__':
unittest.main()
8.7.2 参数化测试
import unittest
import cadquery as cq
class TestParametricBox(unittest.TestCase):
"""参数化盒子测试"""
test_cases = [
(10, 10, 10),
(30, 20, 10),
(50, 50, 50),
(100, 60, 20),
]
def test_box_volumes(self):
"""测试不同尺寸盒子的体积"""
for length, width, height in self.test_cases:
with self.subTest(l=length, w=width, h=height):
box = cq.Workplane("XY").box(length, width, height)
vol = box.val().Volume()
expected = length * width * height
self.assertAlmostEqual(vol, expected, places=1,
msg=f"盒子 {length}x{width}x{height} 体积错误")
if __name__ == '__main__':
unittest.main()
8.8 本章小结
本章介绍了CadQuery二次开发的基础知识:
-
架构理解
- CadQuery代码结构
- 核心类关系
- OCP绑定层
-
扩展方法
- 添加自定义方法
- 创建自定义选择器
- 扩展导出格式
-
OCP操作
- 直接使用OCCT API
- 复杂几何操作
- 访问底层几何
-
插件开发
- 插件结构
- 注册机制
- 插件使用
-
外部集成
- NumPy、Pandas集成
- 可视化
-
调试与测试
- 错误处理
- 日志记录
- 单元测试
通过本章学习,您应该能够:
- 理解CadQuery的内部架构
- 扩展CadQuery功能
- 使用OCP进行高级操作
- 创建CadQuery插件
- 有效调试和测试代码
下一章我们将学习更高级的二次开发技巧和实际项目案例。

浙公网安备 33010602011771号