这是 Studio 1.1.0 的手册,不是最新版本。 最新版本的手册

文档 1.1.0 接口参考

文件格式

_草稿(第 14 轮):正文在写,各节还会补。_

程序

程序是 G 代码文本文件,用 Grbl 1.1 的写法,同匠的控制器认这种写法。这样的文件 Studio 都能读。它的 CAM 写出的程序是这样的:

(Sensor bracket)
(tool T1 flat 6 n3 L20, 6061-T6: S13263, F836, plunge F279, depth of cut 1.20 mm, stepover 2.40 mm)
(program zero: on the blank's top; the machine's top first; T1 is the length reference)
G21 G90 G17 G94 G54
G53 G0 Z0
T1 M6
G53 G0 Z0
M3 S13263
G4 P3 (up to speed)
(face X-2..62 Y-2..42, 0.3 mm deep: 1 layer of 12 passes)
...
M5
M9
G53 G0 Z0
M30
部分是什么
程序头注释:作业的标题;第一把刀、材料和主要切削参数;程序零点在哪里
模式G21 G90 G17 G94 G54:毫米、绝对坐标、XY 平面、每分钟进给、工件坐标 G54
G53 G0 Z…按机床坐标移到 Z 的最高处:开头、每次换刀和结尾。这个高度是回零后 Z 停的地方。
T… M6换刀。控制器在对刀仪上量新刀。
M3 S…、G4 P…开主轴,再暂停一会儿等它转到转速。水冷主轴先开它的水泵(M8)。
特征的注释每个特征开头有一行注释,说明切什么
移动G0、G1,圆弧 G2/G3 带 I 和 J。钻孔用 G81,啄钻用 G83,R 平面在顶面上方 1 mm。
结尾M5、M9、移到最高处、M30

注释写在括号里。行不编号。

Studio 把程序的每个版本存在程序目录(文档/Twinwright Studio/programs)里,以程序名为名的文件夹中:

文件存什么
v0001.nc程序的文本,按存的时候原样
v0001.json它的版本号、时间(UTC)、谁出的、为什么、从哪个版本来、哈希和行数
v0001.ops.json每个特征变成了什么(工序、策略、刀具、行号、转速、进给):装夹单读它

作业和装夹

作业是 CAM 出程序所依据的零件描述,是一个 JSON 对象:

键意思
name程序的名字,比如 bracket.nc
title、description写进程序头和装夹单的文字
material材料,比如 6061-T6;别的标准的牌号也认得
tool主轴上的刀,按规格写(flat 6 n3 L20)
tools刀具表:{"1": "flat 6 n3 L20", "2": "drill 3.4 118 L20"}。不写时用机床的。
cutting覆盖自动选定值的参数:rpm、feed、ap、ae、finish_ap;corner_steps(true 或 false:壁的内角先分步切)
safe_z横移的高度,从工件零点往上算,单位毫米
zero工件零点在哪里:front-left-top(默认)或 center-top
features各个特征,按切的顺序。见出程序。
setups代替 features:每次装夹一个对象
nest代替 features:在板料上排料的零件

每次装夹有自己的 features,并写明零件从上一次装夹怎么过来:

键意思
turnu:绕 X 翻面(前后对调);v:绕 Y 翻面(左右对调)
fixture从这次装夹起换一种夹法(vise、clamps、tape …)
zero重新找的工件零点:front-left-top 或 center-top。翻面或换了夹具又没写时,取 front-left-top;否则沿用上一次装夹的零点。
name、tool、tools、cutting、safe_z这次装夹自己的,覆盖作业的
{"name": "bracket.nc", "title": "Sensor bracket", "material": "6061-T6",
 "tools": {"1": "flat 6 n3 L20", "2": "drill 3.4 118 n2 L20"},
 "features": [
  {"op": "face", "x0": -2, "y0": -2, "x1": 62, "y1": 42, "depth": 0.3, "tool": 1},
  {"op": "pocket", "shape": "rect", "x0": 6, "y0": 6, "x1": 28, "y1": 34, "r": 3, "depth": 4, "tool": 1},
  {"op": "pocket", "shape": "circle", "cx": 44, "cy": 20, "d": "Ø12 H7", "depth": 6, "tool": 1},
  {"op": "drill", "points": [[44, 6], [44, 34]], "depth": 8, "tool": 2}]}

作业的每个版本都存在它出的程序旁边,叫 job-v0001.json:版本号、时间(UTC)、谁出的(operator 或 ai:<客户端>)、备注、它出的程序版本,以及作业本身。分几次装夹的作业还列出每次装夹的程序、出的版本和它的哈希。

录制(MCAP)

--mcap 文件 把一次运行录成 MCAP 文件(mcap.dev,格式版本 0):每类数据一个通道,消息是 JSON,每个通道带它的 JSON Schema,不分块、不压缩,文件末尾有统计和 CRC 的摘要。Studio 的测试按 MCAP 规范把文件读回来核对过;PlotJuggler、Foxglove 这类读 MCAP 的工具应当打得开,但还没实际试过。

  • --rec-period 毫秒 设采样通道多久写一次(不给就是 1 ms)。
  • 录制不改变运行:录着的一次和没录的同一次逐位相同。
  • --play 文件.mcap 在 Studio 里放一段录制,带时间轴:播放、暂停、拖动定位、0.25–10 倍速。放的时候什么都不仿真。

时间

每条消息的记录时间是孪生的虚拟时间,从上电起算的纳秒。多数通道还带 t,是同一个时间,单位秒。采样通道只在内容变了的时候才写一条。

文件头

一条名为 mc1sim 的元数据记录,说这是一次什么样的运行:

键意思
machine机床的名字
drive_model这次运行的驱动模型:L0、L1、L2、L2/3(三质量)或 L3
f_cpu控制器的时钟,Hz
nvs运行开始时控制器所存设置的散列,16 位十六进制
sample_period_ms采样周期
timevirtual, ns from power-on

通道

通道什么时候写字段类型单位意思
/machine采样posnumber[3]mm各轴在哪里:机械本身,不是控制器以为的
mposnumber[3] 或 nullmm控制器的机床位置(MPos);控制器没开时为 null
lostnumber[3]各轴丢了多少步
feednumbermm/min控制器此刻的进给速度
tipnumber[3]mm刀尖相对工件:孪生的误差模型开着时按它的误差移过,关着时和 pos 一样
/drives采样,驱动模型从 L1 起ferr_umnumber[3]µm各驱动器的跟随误差
statestring[3]各驱动器的状态
km1boolean接触器 KM1 吸合
bus48boolean驱动器的 48 V 电源有电
/spindle采样,有变频器时f_outnumberHz变频器的输出频率
rpmnumberrpm主轴的转速
ampsnumberA变频器的输出电流
load_pctnumber%变频器报的负载
torque_nmnumberN·m电机的转矩
bus_vnumberV变频器的直流母线电压
cmd_rpmnumberrpm控制器要的转速
run、pumpboolean变频器在运行;水泵开着
powerstring变频器自己的供电:ready、boot 或 off
faultstring变频器的故障,照它面板上显示的
/controller变化时statestring控制器的状态(没开时为 Off)
alarminteger当前的报警(0:没有)
homedinteger已回零的轴,每轴一位:X 1、Y 2、Z 4
limitsinteger有效的限位输入,每路一位
probe、estop、motor_fault、doorboolean探针、急停、电机故障、门的输入(MC-1 没有门)
onboolean控制器有电
/power变化时mains、v24、mcu、km1、bus48boolean市电;24 V 电源;控制器在运行;KM1;48 V 电源
vfd_mains、vfd、pumpboolean、string、boolean变频器的市电;它的状态(none、ready、boot、off);水泵
/serial/in来的时候textstring控制器收到的字节;控制字符写成 \u 转义
/serial/out来的时候linestring控制器写出的每一行
/events发生的时候textstring事件日志的每一条:标记、供电、驱动器、变频器、碰撞、断线
/cut采样,有毛坯时cuttingboolean正在切下材料;不切时下面的数除 rpm 和 removed_mm3 外都是 0
mrr_mm3_snumbermm³/s材料去除率
ap_mm、ae_mmnumbermm轴向切深和径向切宽
fz_mmnumbermm每齿进给
rpmnumberrpm主轴的转速
fx_n、fy_n、fz_nnumberN沿机床各轴的切削力
torque_nm、power_wnumberN·m、W切削的转矩和功率
kc_j_mm3numberJ/mm³比切削能
removed_mm3numbermm³到此刻一共去掉的材料
/input来的时候linestring会话的输入,一行一行,照 --record 写的(见会话日志)
/shadow来的时候,影子模式下host_snumbers电脑的时间
dirstringt 发往真机,r 从真机来
datastring那些字节,每个字节一个字符 U+0000–U+00FF,一条消息最多 1 KB
/ui/state、/sound给播放用界面的状态帧(变化时每秒 30 次)和声音的帧(变化时每 10 ms 虚拟时间一次)。是 Studio 自己用的:字段随版本可能会变。

引脚波形(VCD)

--vcd 文件 把控制器的引脚写成 VCD 文件(IEEE 1364),GTKWave、PulseView 读得了:作用域 mcu 是控制器的引脚,每个变化记在它发生的那一拍(步进脚画成 $0 微秒宽的脉冲);作用域 machine 是控制器外面的线:各路电源、KM1、变频器、水泵、急停按钮、对刀仪、五只开关。时间刻度 1 ns。

会话日志

--record 文件 把一次会话的输入连同各自的虚拟时间写下来:发送软件发的、实时命令、界面的命令。--replay 文件 把这次会话再跑一遍,逐位相同,前提是同一版本的 Studio、同样的机床文件。--replay 文件 --branch 秒 重放到那一秒,再从那里实时接着走;加 --record 时分叉有自己的日志,从父日志开始(# branch <父日志> <拍数>)。日志是文本;它的行是 Studio 自己用的,随版本可能会变。

世界存档

孪生在两次会话之间留着的东西,就像真机关了再开:各轴停在哪里、毛坯和切过的地方、主轴上的刀、控制器的存储和板上时钟,还有热——关机这段时间里慢慢凉下来。控制器是冷启动:机床要重新回零。

窗口把每台机床的世界放在状态文件夹里(Windows 上是 %LOCALAPPDATA%\Twinwright\Studio):第一台的在 world,其他的在 machines\<编号>\world。--world 目录 用别的文件夹;--world-reset 从出厂状态开始。批量运行、测试、重放、放录制、AI 的后台机器都不留世界,除非给了 --world。同一个世界同时只有一个程序能打开;第二个从出厂状态开始,什么也不存。

文件存的是什么
world.json会话结束时的样子(见下),先写成 world.json.tmp 再改名
stock.snap毛坯的格子,切过的和没切的(二进制,TWSTOCK1)
eeprom.bin控制器的存储:它的设置、偏置和刀具数据,一变就写进去

world.json:

键意思
world格式的版本:1
saved_at、saved_epoch什么时候存的:UTC,以及 1970 年以来的秒数
version存它的 Studio 的版本
pos各轴停在哪里,mm(X、Y、Z)
tool主轴上的刀,写成刀具规格(flat 6 n3 L20)
tool_table作业的刀具表:{"1": "flat 6 n3 L20", ...}
tool_auto换刀时孪生替操作员做:按刀具表装上刀、按循环启动
setup装夹和毛坯,和窗口的“保存装夹”写的一样(null:没有)
cellsstock.snap 里存着毛坯的格子
thermal热:screws(各丝杠高于机架)、frame(高于 20 °C)、ambient(室温高于 20 °C)、part_heat 和 part_offset(工件高于机架:切削带进去的,以及给定的偏置),单位都是 K;spindle_um 是主轴的伸长,单位 µm
rtc板上时钟的读数,s
probe_cal测头最近一次标定的结果:球的直径、有效半径、三瓣和它的相位、环规的直径、最大偏差、什么时候、谁标的(标定过才有)
odometer各轴的 travel_mm 和 reversals,spindle_h(主轴转过的小时数)和 on_h(程序开着的小时数),跨会话累计

下次开机时,各轴还在原处;同一块毛坯带着它的格子回来,刀和刀具表照旧;热按关机的时间凉下来,各部分按各自的时间常数回到静止的状态;板上时钟走过了这段时间。--world-off 秒 指定关了多久(测试用;不给就是存档以来的时间)。启动时给了 --pos、--tool、--stock、--setup、--part,就用它们,不用存档里的。

授权文件

授权文件(.twlic)是一个 UTF-8 的 JSON 对象,用 Ed25519 签名。Studio 在本机核验,不用联网。文件放在哪儿、怎么用:见授权、席位和激活。

下面这个例子取自共用的测试向量,用测试密钥签(kid 是 test),正式发布的版本不认它:

{"v": 1, "kid": "test", "id": "TWL-TEST000001", "edition": "pro", "kind": "subscription", "seats": 1,
 "holder": "Weber Lab, TU Example", "issued": "2026-11-02T10:00:00Z", "valid_until": "2027-11-02T10:00:00Z",
 "renew_after": "2027-10-28T10:00:00Z", "updates_until": null, "fp": ["0123456789abcdef0123456789abcdef"],
 "mc1_serial": null,
 "sig": "h3FTqZnDYtq8Q7_0ZuELLeDrTfyZnkTPgvDWSywTyl7BCJJxGIyhrBQUjYHJiE1_WOWaFarqbveFiMLrEfUyCA"}

字段

字段类型意思
v整数格式版本,是 1。别的版本的文件,Studio 按无效处理。
kid字符串用同匠哪一把密钥签的
id字符串授权编号:TWL- 加 10 位字母和数字。续期时发的就是它。
edition字符串maker 或 pro
kind字符串subscription(订阅)、perpetual(买断)或 owner(MC-1 机主)
seats整数席位数
holder字符串授权给谁:个人或单位的名字,从不放电子邮箱
issued日期时间签发时间,UTC,YYYY-MM-DDTHH:MM:SSZ
valid_until日期时间或 null订阅:已付费那一期的结束。买断:第一个文件里是退款期的结束,永久文件里是 null。
renew_after日期时间或 nullStudio 从什么时候开始试着续期
updates_until日期或 null买断、MC-1 机主:这一天以前发布的版本能用付费功能
fp字符串数组绑定的电脑:它们指纹的散列,每个席位最多三个
mc1_serial字符串或 nullMC-1 机主:机器的序列号。Studio 运行时不查机器。
sig字符串其余字段的 Ed25519 签名,base64url,不补 =

Studio 不认识的字段也在签名里;Studio 不管它的意思。

签名的字节

签名覆盖去掉 sig 以后的文件,按规范的写法写出来:

  • 键按 Unicode 码点排序;
  • 没有空白;
  • 字符串用 JSON 最短的转义;
  • 整数不带小数点;不许有别的数,也不许同一个键出现两次。

Studio 核验签名之前,先把文件按这个写法写一遍。所以文件里的空格、换行和键的顺序都不要紧;改了任何一个值,核验就不过。

指纹的散列

散列是对这些字节算 SHA-256 后取前 32 位十六进制:twinwright-fp-v1、一个零字节、电脑的 ID。

系统电脑的 ID
WindowsHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography 下的 MachineGuid
Linux/etc/machine-id

两样都没有时,Studio 生成一个随机 ID,存在授权文件夹里(machine.id);删了它就等于换了一台电脑。ID 本身从不离开电脑。

例如 ID 8b1c3a0e-5f2d-4c7a-9e61-2d0f4b7a1c55 的散列是 0a2d31bb82176516415efa7b3a340562。

请求码

离线激活用的请求码,是规范写法的 UTF-8 JSON {"fp": <散列>, "version": <Studio 的版本>} 做 base64url,不补 =。例如散列 0123456789abcdef0123456789abcdef、版本 1.0.0 得到:

eyJmcCI6IjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwidmVyc2lvbiI6IjEuMC4wIn0

TwinwrightStudio.exe --licence-request 打出这台电脑的请求码。

试用文件

试用就是一份授权文件,和别的授权文件一样由同匠的授权服务器签名:"kind": "trial",fp 里是这台电脑的散列,valid_until 是开始试用后 30 天。试用文件从不续期。手改过的试用文件不再有效。

机床档案

机床档案的每一项,带 MC-1 的值:机床档案格式。