(从眼科处方数据 → 镜片曲面矢高模型 → 慢刀伺服螺旋刀路 → G代码 → FANUC 机床通信下发的完整流程)核心思路是将处方中的球镜、柱镜、轴位转换为环曲面矢高方程,在 X--Z--C 三轴柱坐标下生成等角度螺旋刀位点,后置处理为 G 代码后通过 FOCAS 下发。
```python
"""
lens_surfacing.py
根据眼科检查数据自动生成镜片打磨 G 代码并下发至 FANUC 机床
适用:慢刀伺服(STS)车削/打磨,X--Z--C 三轴联动
"""
import math
import time
from dataclasses import dataclass
from typing import List, Tuple, Optional
----------------------------------------------------------------------
1. 眼科处方数据模型
----------------------------------------------------------------------
@dataclass
class OphthalmicRx:
"""眼科处方( spectacle plane,单位:屈光度 D)"""
sphere: float # 球镜 S
cylinder: float # 柱镜 C(负柱镜 convention,若为正需转换)
axis_deg: float # 轴位 α,0--180°
lens_diameter_mm: float = 70.0
base_curve_D: float = 6.0 # 后表面基础曲率(可根据镜片类型调整)
refractive_index: float = 1.530 # 镜片材料折射率
def effective_powers(self) -> Tuplefloat, float:
"""
将处方转换为两个主子午线上的屈光力(D)。
负柱镜形式:F1 = S, F2 = S + C
"""
if self.cylinder > 0:
若处方为正柱镜,先转为负柱镜形式
sphere_new = self.sphere + self.cylinder
cylinder_new = -self.cylinder
axis_new = (self.axis_deg + 90) % 180
else:
sphere_new, cylinder_new, axis_new = self.sphere, self.cylinder, self.axis_deg
return sphere_new, sphere_new + cylinder_new
----------------------------------------------------------------------
2. 镜片曲面矢高模型(环曲面/复曲面)
----------------------------------------------------------------------
class ToricSurface:
"""
环曲面矢高模型:前后表面可采用双锥面(biconic)或环曲面方程。
此处使用标准环曲面矢高方程,包含二次曲面项reference:1。
z = (Cx x² + Cy y²) / 1 + sqrt(1 - (1+kx)Cx²x² - (1+ky)Cy²y²)
- 高阶非球面修正(可选项)
"""
def init(self, Rx: OphthalmicRx, surface: str = "front"):
self.surface = surface
F1, F2 = Rx.effective_powers()
n = Rx.refractive_index
由屈光力反推曲率半径(mm):F = (n-1)/R * 1000
R1 = 1000 * (n - 1) / F1 if abs(F1) > 1e-6 else 1e6
R2 = 1000 * (n - 1) / F2 if abs(F2) > 1e-6 else 1e6
if surface == "back":
后表面基础曲率参与,符号相反
R1 = -1000 * (n - 1) / (Rx.base_curve_D + F1)
R2 = -1000 * (n - 1) / (Rx.base_curve_D + F2)
self.Cx = 1.0 / R1
self.Cy = 1.0 / R2
轴位旋转:将柱镜轴位对齐到 Y 轴,X 轴为其垂直方向
self.axis_rad = math.radians(Rx.axis_deg)
def sag(self, x: float, y: float) -> float:
"""计算点 (x, y) 处的矢高 z(mm)"""
旋转坐标,使柱镜轴位对齐到 Y 轴
ct, st = math.cos(self.axis_rad), math.sin(self.axis_rad)
xr = ct * x + st * y
yr = -st * x + ct * y
kx = ky = 0.0 # 二次曲面常数,默认球面;非球面时可调整
r2 = (1 + kx) * Cx**2 * xr**2 + (1 + ky) * Cy**2 * yr**2
denom = 1.0 + math.sqrt(max(0.0, 1.0 - r2))
if denom < 1e-9:
return 0.0
z = (Cx * xr**2 + Cy * yr**2) / denom
return z
----------------------------------------------------------------------
3. 螺旋刀路生成(柱坐标 X--Z--C)
----------------------------------------------------------------------
@dataclass
class ToolpathPoint:
x_mm: float # 径向位置
z_mm: float # 轴向位置(矢高 + 刀具补偿)
c_deg: float # C 轴角度
feed: float # 进给速度 mm/min
class SpiralToolpathGenerator:
"""
慢刀伺服螺旋刀路:在极坐标 (ρ, θ) 上等角度离散,逐圈径向进给。
X = ρ, Z = sag(ρ cosθ, ρ sinθ) + tool_offset
C = θ(度)
"""
def init(self, surface: ToricSurface, R_max: float,
radial_step: float = 0.5, # mm/圈
angle_step_deg: float = 2.0,
tool_offset: float = 0.0,
feed_rough: float = 80.0,
feed_finish: float = 40.0):
self.surface = surface
self.R_max = R_max
self.radial_step = radial_step
self.angle_step_deg = angle_step_deg
self.tool_offset = tool_offset
self.feed_rough = feed_rough
self.feed_finish = feed_finish
def generate(self) -> ListToolpathPoint:
points: ListToolpathPoint = \[\]
r = self.radial_step
while r <= self.R_max + 1e-9:
每圈的 C 轴角度分段数,随半径增大适当加密以保持残高均匀
circumference = 2 * math.pi * r
n_seg = max(8, int(circumference / (self.radial_step * 0.8)))
但整体用统一的角步长控制,保证代码简洁;可改为变角步长
n_seg = max(12, int(360 / self.angle_step_deg))
for i in range(n_seg + 1):
theta_deg = i * 360.0 / n_seg
theta = math.radians(theta_deg)
x = r * math.cos(theta)
y = r * math.sin(theta)
z_sag = self.surface.sag(x, y)
z = z_sag + self.tool_offset
粗加工与精加工进给区分:最后 2 圈视为精加工
remaining_turns = (self.R_max - r) / self.radial_step
feed = self.feed_finish if remaining_turns <= 2 else self.feed_rough
points.append(ToolpathPoint(x_mm=r, z_mm=z,
c_deg=theta_deg % 360, feed=feed))
r += self.radial_step
return points
----------------------------------------------------------------------
4. G 代码后处理
----------------------------------------------------------------------
class GCodePostProcessor:
"""
将柱坐标刀路点转换为 FANUC 兼容 G 代码。
假设机床配置:X 轴径向,Z 轴轴向,C 轴为回转轴(度)。
"""
def init(self, program_number: int = 1000,
safe_z: float = 5.0,
spindle_rpm: int = 2000):
self.program_number = program_number
self.safe_z = safe_z
self.spindle_rpm = spindle_rpm
def generate(self, points: ListToolpathPoint) -> str:
lines = \[\]
lines.append(f"O{self.program_number} (LENS SURFACING)")
lines.append("G21 G90 G40 G80") # 公制、绝对坐标、取消补偿
lines.append("G00 Z{:.3f}".format(self.safe_z))
lines.append("M03 S{}".format(self.spindle_rpm))
lines.append("G00 X0. C0.")
lines.append("G00 Z{:.3f}".format(points0.z_mm + 1.0))
prev_c = 0.0
for p in points:
处理 C 轴角度连续性,避免 0→360 跳变
c = p.c_deg
if prev_c - c > 180:
c += 360
elif c - prev_c > 180:
c -= 360
lines.append(
f"G01 X{p.x_mm:.3f} Z{p.z_mm:.3f} C{c:.3f} F{p.feed:.1f}"
)
prev_c = c
lines.append("G00 Z{:.3f}".format(self.safe_z))
lines.append("G00 X0. C0.")
lines.append("M05")
lines.append("M30")
return "\n".join(lines)
----------------------------------------------------------------------
5. FANUC FOCAS 通信下发(可选,依赖 pyfanuc / fwlib)
----------------------------------------------------------------------
class FanucCommunicator:
"""
通过 FOCAS 将 G 代码写入机床宏变量或上传程序。
简化方案:将 G 代码按行写入 R 区(内部继电器)或宏变量,由机床端 PMC 程序读取并执行。
实际部署时可直接使用 FTP/CF 卡上传 .NC 文件;此处演示 FOCAS 写入宏变量的方式reference:2。
"""
def init(self, ip: str, port: int = 8193, timeout: int = 10):
self.ip = ip
self.port = port
self.timeout = timeout
self._controller = None
def connect(self):
try:
from pyfanuc import FocasController
self._controller = FocasController(self.ip, self.port, self.timeout)
self._controller.connect()
return True
except Exception as e:
print(f"FOCAS 连接失败:{e},将回退为本地文件输出")
return False
def send_program_as_macros(self, gcode: str, start_macro: int = 500):
"""
将 G 代码按行写入宏变量 #500, #501, ...(每行一个字符串宏变量不现实,
实际 FANUC 宏变量多为数值型;此处仅作演示。生产环境建议用 FTP 上传 .NC 文件。)
"""
if not self._controller:
return False
lines = gcode.splitlines()
for idx, line in enumerate(lines):
实际写入需根据 FANUC 宏变量类型处理;pyfanuc 的 write_macro 方法可写入数值
此处仅示意接口调用位置
macro_num = start_macro + idx
self._controller.write_macro(macro_num, line) # 字符串宏通常不可行,仅供示意
print(f"FOCAS 示意 宏 #{macro_num} ← {line:40}")
return True
def close(self):
if self._controller:
self._controller.disconnect()
----------------------------------------------------------------------
6. 端到端流程
----------------------------------------------------------------------
def surface_lens(rx: OphthalmicRx,
radial_step: float = 0.5,
tool_offset: float = 0.0,
output_file: str = "lens_surfacing.NC",
fanuc_ip: Optionalstr = None) -> str:
"""
端到端函数:处方 → 矢高模型 → 螺旋刀路 → G 代码 → 文件/FANUC 下发
"""
1) 曲面模型(前表面示例;后表面传入 surface="back")
surface = ToricSurface(rx, surface="front")
2) 刀路生成
generator = SpiralToolpathGenerator(
surface=surface,
R_max=rx.lens_diameter_mm / 2.0,
radial_step=radial_step,
angle_step_deg=2.0,
tool_offset=tool_offset
)
points = generator.generate()
3) G 代码后处理
post = GCodePostProcessor(program_number=1000)
gcode = post.generate(points)
4) 输出到本地文件
with open(output_file, "w", encoding="ascii") as f:
f.write(gcode)
print(f"OK G 代码已写入 {output_file},共 {len(points)} 个刀位点")
5) 可选:FANUC 通信下发
if fanuc_ip:
comm = FanucCommunicator(fanuc_ip)
if comm.connect():
comm.send_program_as_macros(gcode)
comm.close()
return gcode
----------------------------------------------------------------------
使用示例
----------------------------------------------------------------------
if name == "main":
示例处方:球镜 -2.50D,柱镜 -1.25D,轴位 175°
rx = OphthalmicRx(
sphere=-2.50,
cylinder=-1.25,
axis_deg=175.0,
lens_diameter_mm=70.0,
base_curve_D=6.0,
refractive_index=1.530
)
生成 G 代码并保存;若有 FANUC 机床,传入 IP 即可尝试下发
gcode_text = surface_lens(
rx=rx,
radial_step=0.4,
tool_offset=0.05, # 刀具半径补偿/安全余量,需按实际刀具标定
output_file="lens_surfacing_demo.NC",
fanuc_ip=None # 例如 "192.168.1.100"
)
print("G 代码预览(前 20 行):")
print("\n".join(gcode_text.splitlines():20))
```
关键实现说明
- 处方到曲面的数学桥梁
代码将负柱镜形式的处方拆解为两个主子午线屈光力 F₁ = S、F₂ = S + C,再由 F = (n−1)/R 反推曲率半径,代入环曲面矢高方程。轴位通过坐标旋转对齐到 Y 轴,使柱镜方向得到正确处理。若处方为正柱镜,代码自动转换为负柱镜形式,保证矢高模型的一致性。
- 螺旋刀路与慢刀伺服匹配
慢刀伺服的核心是 X--Z--C 三轴联动,刀具沿螺旋轨迹逐圈径向进给。代码以 radial_step 控制每圈径向进给量,以 angle_step_deg 控制每圈角度分段,在每个 (ρ, θ) 处计算 X = ρ、Z = sag(ρcosθ, ρsinθ) + tool_offset、C = θ。tool_offset 用于集成刀具半径补偿和安全余量,实际部署时必须根据刀具形状标定。
- G 代码后处理与 C 轴连续性
FANUC 系统中 C 轴通常以度为单位,代码对 C 轴角度做了连续性修正,防止在 0°/360° 边界产生反转跳变。粗精加工通过进给速度区分:最后两圈使用较低的 finish 进给,其余使用 rough 进给。
- 通信下发方式的选择
示例使用 pyfanuc 的 FOCAS 接口演示连接和宏变量写入。但需注意:将 G 代码逐行写入数值型宏变量并非生产环境的可靠方式。实际部署中,更常见的做法是通过 FTP/CF 卡上传 .NC 文件,或由 PMC 程序从 D 数据表读取加工参数后再调用固定子程序。代码中 FanucCommunicator 的主要作用是展示通信入口,生产环境应替换为文件传输或 D 数据表写入方案。
- 实际部署前必须补充的环节
· 刀具半径补偿:当前 tool_offset 是常量,实际砂轮/刀具在曲面不同位置的接触点会变化,应基于等距面或法向偏置计算逐点补偿量。
· 干涉检测:镜片边缘曲率变化大时,需检测刀具与工件的干涉。
· 机床坐标系标定:X 零位、Z 对刀点、C 轴相位均需与代码中的假设一致。
· 材料去除模型:打磨不同于车削,进给速度与去除量的关系需结合赫兹接触或 Preston 方程标定。
文章仅供参考用。