_Borrador (ronda 14): el texto se está escribiendo; las secciones aún pueden crecer._
Un programa es un archivo de texto de código G en el dialecto de Grbl 1.1, que habla el núcleo propio de Twinwright. Studio lee cualquier archivo de este tipo. Los programas que escribe su CAM tienen este aspecto:
(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 |
| Parte | Qué es |
|---|---|
| Cabecera | Comentarios: el título del trabajo; la primera herramienta, el material y los datos de corte principales; dónde está el cero del programa |
| Modos | G21 G90 G17 G94 G54: milímetros, absoluto, el plano XY, avance por minuto, decalaje de origen G54 |
G53 G0 Z… | Un movimiento al extremo superior de Z, en coordenadas de máquina: al principio, en cada cambio de herramienta y al final. La altura es aquella en la que Z queda al hacer la referencia. |
T… M6 | Un cambio de herramienta. El controlador mide la herramienta nueva en el sensor de longitud de herramienta. |
M3 S…, G4 P… | El husillo en marcha y después un tiempo de espera mientras alcanza la velocidad. La bomba de un husillo refrigerado por agua (M8) va primero. |
| Comentarios de elemento | Cada elemento empieza con un comentario que dice qué corta |
| Movimientos | G0, G1 y arcos G2/G3 con I y J. El taladrado usa G81, o G83 con descargas, con su plano R 1 mm por encima de la parte superior. |
| Final | M5, M9, el movimiento al extremo superior, M30 |
Los comentarios van entre paréntesis. Las líneas no llevan números.
Studio guarda cada versión de un programa en la carpeta de programas (Documentos/Twinwright Studio/programs), en una carpeta con el nombre del programa:
| Archivo | Qué contiene |
|---|---|
v0001.nc | El texto del programa, tal como se guardó |
v0001.json | Su versión, la hora (UTC), quién lo hizo, por qué, la versión de la que procede, su hash y sus líneas |
v0001.ops.json | En qué se convirtió cada elemento (operación, estrategia, herramienta, líneas, velocidad, avance): lo lee una hoja de amarre |
Un trabajo es la descripción de una pieza a partir de la cual el CAM genera programas. Es un objeto JSON:
| Clave | Significado |
|---|---|
name | El nombre del programa, por ejemplo bracket.nc |
title, description | Textos para la cabecera del programa y la hoja de amarre |
material | El material, por ejemplo 6061-T6; se entienden los nombres de otras normas |
tool | La herramienta del husillo, como especificación (flat 6 n3 L20) |
tools | La tabla de herramientas: {"1": "flat 6 n3 L20", "2": "drill 3.4 118 L20"}. Si se omite: la de la máquina. |
cutting | Valores que sustituyen a los elegidos: rpm, feed, ap, ae, finish_ap; corner_steps (true o false: las esquinas interiores de una pared se cortan primero por escalones) |
safe_z | La altura a la que se hacen los movimientos de traslado, en milímetros sobre el origen de pieza |
zero | Dónde está el origen de pieza: front-left-top (el valor por defecto) o center-top |
features | Los elementos, en el orden en que se cortan. Consulte Crear programas. |
setups | En lugar de features: un objeto por cada amarre |
nest | En lugar de features: piezas anidadas en una placa |
Un amarre tiene sus propios features y dice cómo llega la pieza a él desde el amarre anterior:
| Clave | Significado |
|---|---|
turn | u: volteada alrededor de X (el frente y la parte trasera se intercambian); v: alrededor de Y (izquierda y derecha) |
fixture | Se sujeta de otro modo a partir de este amarre (vise, clamps, tape …) |
zero | El origen de pieza, determinado de nuevo: front-left-top o center-top. Si se omite tras un volteo o un nuevo utillaje: front-left-top; en otro caso, el origen del amarre anterior. |
name, tool, tools, cutting, safe_z | Los propios del amarre, por encima de los del trabajo |
{"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}]} |
Cada versión de un trabajo se guarda junto al programa que generó, como job-v0001.json: su versión, la hora (UTC), quién lo hizo (operator o ai:<client>), una nota, la versión del programa que generó y el propio trabajo. Un trabajo descrito en amarres lista además el programa de cada amarre, la versión generada y su hash.
--mcap FILE graba una ejecución en un archivo MCAP (mcap.dev, formato versión 0): un canal para cada tipo de datos, mensajes JSON, cada canal con su esquema JSON, sin fragmentar ni comprimir, y un resumen al final con las estadísticas y un CRC. Las pruebas de Studio vuelven a leer los archivos y los comparan con la especificación de MCAP; las herramientas que leen MCAP, como PlotJuggler y Foxglove, están pensadas para abrirlos, pero todavía no se ha probado.
--rec-period MS fija con qué frecuencia se escriben los canales muestreados (1 ms si no se indica).--play FILE.mcap reproduce una grabación en Studio, con una línea de tiempo: reproducir, pausar, buscar, 0.25–10 veces la velocidad. No se simula nada mientras se reproduce.La hora de registro de cada mensaje es el tiempo virtual del gemelo, en nanosegundos desde el encendido. La mayoría de los canales llevan además t, el mismo tiempo en segundos. Un canal muestreado escribe un mensaje solo cuando algo en él ha cambiado.
Un registro de metadatos llamado mc1sim dice en qué consistió la ejecución:
| Clave | Significado |
|---|---|
machine | El nombre de la máquina |
drive_model | El modelo de accionamiento de la ejecución: L0, L1, L2, L2/3 (tres masas) o L3 |
f_cpu | El reloj del controlador, Hz |
nvs | Un hash de los ajustes guardados del controlador al comenzar la ejecución, 16 dígitos hexadecimales |
sample_period_ms | El periodo de muestreo |
time | virtual, ns from power-on |
| Canal | Escritura | Campo | Tipo | Unidad | Significado |
|---|---|---|---|---|---|
/machine | muestreado | pos | número[3] | mm | Dónde está cada eje: la mecánica, no la idea que tiene de ello el controlador |
mpos | número[3] o null | mm | La posición de máquina del controlador (MPos); null mientras el controlador está apagado | ||
lost | número[3] | Los pasos que ha perdido cada eje | |||
feed | número | mm/min | La velocidad de avance del controlador ahora | ||
tip | número[3] | mm | La punta de la herramienta respecto a la pieza: con el modelo de errores del gemelo activado, desplazada por sus errores; desactivado, igual que pos | ||
/drives | muestreado, con un modelo de accionamiento desde L1 | ferr_um | número[3] | µm | El error de seguimiento de cada accionamiento |
state | cadena[3] | El estado de cada accionamiento | |||
km1 | booleano | El contactor KM1 cerrado | |||
bus48 | booleano | La alimentación de 48 V de los accionamientos activada | |||
/spindle | muestreado, con un variador del husillo | f_out | número | Hz | La frecuencia de salida del variador |
rpm | número | rpm | La velocidad del husillo | ||
amps | número | A | La corriente de salida del variador | ||
load_pct | número | % | La carga tal como la indica el variador | ||
torque_nm | número | N·m | El par del motor | ||
bus_v | número | V | La tensión del bus de CC del variador | ||
cmd_rpm | número | rpm | La velocidad que pide el controlador | ||
run, pump | booleano | El variador en marcha; la bomba encendida | |||
power | cadena | La alimentación propia del variador: ready, boot u off | |||
fault | cadena | El fallo del variador, tal como lo muestra su pantalla | |||
/controller | al cambiar | state | cadena | El estado del controlador (Off mientras está apagado) | |
alarm | entero | La alarma en la que está (0: ninguna) | |||
homed | entero | Los ejes con la referencia hecha, un bit cada uno: X 1, Y 2, Z 4 | |||
limits | entero | Las entradas de límite que están activas, un bit cada una | |||
probe, estop, motor_fault, door | booleano | Las entradas del palpador, de la parada de emergencia, del fallo de motor y de la puerta (el MC-1 no tiene puerta) | |||
on | booleano | El controlador encendido | |||
/power | al cambiar | mains, v24, mcu, km1, bus48 | booleano | La red eléctrica; la alimentación de 24 V; el controlador en marcha; KM1; la alimentación de 48 V | |
vfd_mains, vfd, pump | booleano, cadena, booleano | La red del variador del husillo; su estado (none, ready, boot, off); la bomba | |||
/serial/in | según llega | text | cadena | Los bytes que recibió el controlador; los caracteres de control como secuencias de escape \u | |
/serial/out | según llega | line | cadena | Cada línea que escribió el controlador | |
/events | según ocurren | text | cadena | Cada entrada del registro de eventos: marcas, la alimentación, los accionamientos, el variador del husillo, colisiones, cables rotos | |
/cut | muestreado, con un bruto | cutting | booleano | Se arranca material; mientras no es así, las cifras siguientes valen 0, salvo rpm y removed_mm3 | |
mrr_mm3_s | número | mm³/s | La tasa de arranque de material | ||
ap_mm, ae_mm | número | mm | La profundidad axial y el ancho radial del corte | ||
fz_mm | número | mm | El avance por diente | ||
rpm | número | rpm | La velocidad del husillo | ||
fx_n, fy_n, fz_n | número | N | La fuerza de corte a lo largo de los ejes de la máquina | ||
torque_nm, power_w | número | N·m, W | El par y la potencia de corte | ||
kc_j_mm3 | número | J/mm³ | La energía de corte específica | ||
removed_mm3 | número | mm³ | El material arrancado hasta ahora | ||
/input | según llega | line | cadena | Las entradas de la sesión, línea a línea tal como las escribe --record (consulte Registros de sesión) | |
/shadow | según llega, en modo sombra | host_s | número | s | La hora del equipo |
dir | cadena | t hacia la máquina real, r desde ella | |||
data | cadena | Los bytes, cada uno como un carácter U+0000–U+00FF, hasta 1 KB por mensaje | |||
/ui/state, /sound | para el reproductor | La trama de estado de las vistas (30 por segundo mientras cambia) y la trama del sonido (cada 10 ms de tiempo virtual cuando cambia). Son propias de Studio: sus campos pueden cambiar de una versión a otra. |
--vcd FILE escribe los pines del controlador como un archivo VCD (IEEE 1364), que leen GTKWave y PulseView: el ámbito mcu contiene los pines del controlador, cada cambio en el tic de reloj en que ocurre (los pines de paso como pulsos de $0 microsegundos); el ámbito machine contiene las líneas ajenas al controlador: las alimentaciones, KM1, el variador del husillo, la bomba, el botón de parada de emergencia, el sensor de longitud de herramienta, los cinco interruptores. La escala de tiempo es 1 ns.
--record FILE escribe las entradas de una sesión, cada una con su tiempo virtual: lo que enviaron los programas de envío, los comandos en tiempo real, los comandos de las vistas. --replay FILE ejecuta la sesión de nuevo, bit a bit, con la misma versión de Studio y los mismos archivos de máquina. --replay FILE --branch S la reproduce hasta S segundos y continúa en vivo desde ahí; con --record, la rama tiene un registro propio, que parte del registro de origen (# branch <parent> <ticks>). Un registro es texto; sus líneas son propias de Studio y pueden cambiar de una versión a otra.
Lo que el gemelo conserva entre sesiones, como hace una máquina real cuando se apaga y se vuelve a encender: dónde están los ejes, el bruto con sus cortes, la herramienta del husillo, la memoria del controlador y el reloj de la placa, y el calor, que se enfría mientras la máquina está apagada. El controlador arranca en frío: se vuelve a hacer la referencia de la máquina.
La ventana guarda el mundo de cada máquina en la carpeta de estado (en Windows %LOCALAPPDATA%\Twinwright\Studio): el de la primera máquina en world, los de las demás en machines\<id>\world. --world DIR usa otra carpeta; --world-reset parte del estado de fábrica. Las ejecuciones por lotes, las pruebas, las reproducciones, las grabaciones reproducidas y las máquinas en segundo plano de la IA no guardan ningún mundo, salvo que se indique --world. Un solo programa a la vez abre un mundo; un segundo parte del estado de fábrica y no guarda nada.
| Archivo | Qué contiene |
|---|---|
world.json | El final de la sesión (más abajo), escrito primero en world.json.tmp y luego renombrado |
stock.snap | Las celdas del bruto, cortadas o no (binario, TWSTOCK1) |
eeprom.bin | La memoria guardada del controlador: sus ajustes, decalajes y datos de herramienta, escrita de inmediato a medida que cambia |
world.json:
| Clave | Significado |
|---|---|
world | La versión del formato: 1 |
saved_at, saved_epoch | Cuándo se guardó: UTC, y segundos desde 1970 |
version | La versión de Studio que lo guardó |
pos | Dónde estaban los ejes, mm (X, Y, Z) |
tool | La herramienta del husillo, como especificación (flat 6 n3 L20) |
tool_table | La tabla de herramientas del trabajo: {"1": "flat 6 n3 L20", ...} |
tool_auto | El gemelo hace de operador en un cambio de herramienta: pone la herramienta de la tabla y pulsa inicio de ciclo |
setup | La sujeción y el bruto, tal como los escribe Guardar amarre en la ventana (null: ninguno) |
cells | stock.snap contiene las celdas del bruto |
thermal | El calor: screws (cada husillo de bolas por encima del bastidor), frame (por encima de 20 °C), ambient (la sala por encima de 20 °C), part_heat y part_offset (la pieza por encima del bastidor: lo que aportan los cortes y un decalaje indicado), todo en K; spindle_um, el crecimiento del husillo en µm |
rtc | La lectura del reloj de la placa, s |
probe_cal | El palpador tal como se calibró por última vez: el diámetro de la bola, su radio efectivo, la lobulación y su fase, el diámetro del anillo patrón, la mayor desviación, cuándo y por quién (solo una vez calibrado) |
odometer | travel_mm y reversals de cada eje, spindle_h (las horas del husillo) y on_h (las horas del programa), acumulados a lo largo de las sesiones |
En el siguiente arranque los ejes están donde estaban; vuelve el mismo bruto con sus celdas, y la herramienta y la tabla; el calor se ha enfriado durante el tiempo que la máquina estuvo apagada, cada parte hacia su estado de reposo con su propia constante de tiempo; el reloj de la placa ha seguido avanzando. --world-off S indica cuánto tiempo estuvo apagada la máquina (para pruebas; de lo contrario, el tiempo transcurrido desde que se guardó). --pos, --tool, --stock, --setup y --part indicados al arrancar prevalecen sobre el archivo.
Un archivo de licencia (.twlic) es un objeto JSON en UTF-8, firmado con Ed25519. Studio lo comprueba en el equipo, sin red. Dónde están los archivos y cómo se usan: Licencias, puestos y activación.
Este ejemplo procede de los vectores de prueba compartidos. Está firmado con la clave de prueba (kid test), que las compilaciones publicadas no aceptan:
{"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"} |
| Campo | Tipo | Significado |
|---|---|---|
v | entero | La versión del formato: 1. Studio trata un archivo de otra versión como no válido. |
kid | cadena | Cuál de las claves de Twinwright firmó el archivo |
id | cadena | El número de la licencia: TWL- y 10 letras y dígitos. La renovación lo envía. |
edition | cadena | maker o pro |
kind | cadena | subscription, perpetual (compra perpetua) o owner (propietario del MC-1) |
seats | entero | El número de puestos |
holder | cadena | A quién corresponde la licencia: una persona o una organización. Nunca una dirección de correo electrónico. |
issued | fecha y hora | Cuándo se firmó el archivo, UTC, YYYY-MM-DDTHH:MM:SSZ |
valid_until | fecha y hora, o null | Una suscripción: el final del periodo pagado. Compra perpetua: el final del periodo de reembolso en el primer archivo, null en el permanente. |
renew_after | fecha y hora, o null | Desde cuándo intenta Studio renovar el archivo |
updates_until | fecha, o null | Compra perpetua, propietario del MC-1: las versiones publicadas hasta este día tienen las funciones de pago |
fp | lista de cadenas | Los equipos a los que está vinculada la licencia: los hashes de sus huellas, hasta tres por puesto |
mc1_serial | cadena, o null | Propietario del MC-1: el número de serie de la máquina. Studio no comprueba la máquina cuando se ejecuta. |
sig | cadena | La firma Ed25519 de los demás campos, en base64url sin relleno |
Un campo que Studio no conoce forma parte de los bytes firmados, y Studio ignora lo que significa.
La firma cubre el archivo sin sig, escrito en una forma canónica:
Studio escribe el archivo en esta forma antes de comprobar la firma. Por tanto, los espacios, los saltos de línea y el orden de las claves del archivo no importan. Un valor cambiado hace fallar la comprobación.
El hash son los primeros 32 dígitos hexadecimales de SHA-256 sobre los bytes twinwright-fp-v1, un byte cero y el ID del equipo:
| Sistema | El ID del equipo |
|---|---|
| Windows | El valor MachineGuid en HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography |
| Linux | /etc/machine-id |
Si no hay ninguno de los dos, Studio crea un ID aleatorio y lo guarda en la carpeta de licencias (machine.id); al borrarlo, el equipo pasa a ser otro distinto. El ID en sí nunca sale del equipo.
Por ejemplo, el ID 8b1c3a0e-5f2d-4c7a-9e61-2d0f4b7a1c55 da el hash 0a2d31bb82176516415efa7b3a340562.
El código de solicitud para la activación sin conexión es el JSON canónico en UTF-8 {"fp": <the hash>, "version": <Studio's version>}, en base64url sin relleno =. Por ejemplo, el hash 0123456789abcdef0123456789abcdef y la versión 1.0.0 dan:
eyJmcCI6IjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwidmVyc2lvbiI6IjEuMC4wIn0 |
TwinwrightStudio.exe --licence-request imprime el código de solicitud de este equipo.
Una prueba es un archivo de licencia como los demás, firmado por el servidor de licencias de Twinwright: "kind": "trial", el hash de este equipo en fp y valid_until 30 días después de empezar la prueba. Nunca se renueva. Un archivo de prueba modificado a mano deja de ser válido.
Todas las claves del archivo de una máquina, con los valores del MC-1: Esquema del perfil de máquina.