Skip to content

Automatic workholding

Ctrl models each automatic workholding device as an independently indexed peripheral. A device may be a power chuck, collet, expanding mandrel, or another mechanism that supports external and internal grip configurations. A part program can select the grip configuration before it asks the machine controller to clamp or unclamp the part.

Grip selection is lasting machine intent. Ctrl publishes and persists it, while the machine action controller owns the physical mechanism, pressure, feedback, and interlocks.

Set the number of automatic workholding devices in the machine INI:

[CTRL]
AUTOMATIC_WORKHOLDING_DEVICES = 1

The device count must be a non-negative integer. Omitting it or setting it to 0 disables automatic-workholding integration. A configured device is available on any machine type; explicit peripheral configuration, rather than the machine’s axis layout, controls whether Ctrl registers the integration.

Devices use stable, zero-based machine-local indices. Device 0 is the first configured workholding mechanism, device 1 is the second, and so on. A workholding index does not identify or imply a LinuxCNC spindle or axis.

Ctrl reserves these commands for automatic workholding:

Code Intent
M10 Unclamp the selected workholding device
M11 Clamp the selected workholding device
M14 P<device> Q0 Select external/OD gripping for the indexed device
M14 P<device> Q1 Select internal/ID gripping for the indexed device

M14 requires both words: P is a configured, zero-based workholding device index, and Q is exactly 0 or 1. Missing arguments, fractional or unconfigured indices, and other mode values must fail before an executor request. For example, M14 P1 Q1 selects internal grip on device 1. Every device uses this same command; no per-device M-code configuration is needed.

M10 and M11 remain argument-free; their remap endpoint and machine wiring choose the target device.

M14 changes the grip configuration; they do not move the mechanism. The next M10 or M11 asks the workholding controller for a semantic result, and that controller translates the request into physical motion:

Grip configuration M10 unclamp M11 clamp
External/OD Open outward Close inward
Internal/ID Retract inward Expand outward

This keeps the part program independent of valve polarity and cylinder layout. The machine controller must preserve the requested clamped or unclamped condition after the executor transaction finishes.

For each configured index N, Ctrl provides:

Interface Meaning
INI [CTRL] AUTOMATIC_WORKHOLDING_DEVICES Declares the number of indexed devices
ctrl.workholding.N.grip-internal False for external gripping; true for internal gripping
Executor ctrl.executor.workholding.N.grip.* Applies an M14 P<N> Q<mode> grip-selection request

Selecting a grip mode in the operator panel submits M14 P<N> Q0 or M14 P<N> Q1 and waits for interpreter completion. The same machine remap handles operator-panel requests and part-program commands. Put grip-selection interlocks before the remap requests the grip executor; the executor only applies an accepted selection.

Ctrl persists the selected grip mode for each indexed device. At startup, restoration waits for the same powered-on, homed, idle readiness used for tool, feed, and RPM settings, then runs each configured device’s grip-selection M-code and requires successful interpreter completion. Restoration therefore uses the same remap interlocks. It never issues clamp or unclamp commands, and a rejected restoration is reported rather than applied directly.

Connect ctrl.workholding.N.grip-internal directly to the machine-owned workholding controller. That controller combines the pin with its semantic clamp or unclamp request. Do not use this pin as a direct valve command.

Complete the shared peripheral remap setup first. Add these commands to the machine’s existing [RS274NGC] section:

REMAP = M10 modalgroup=10 python=m10_workholding_unclamp
REMAP = M11 modalgroup=10 python=m11_workholding_clamp
REMAP = M14 modalgroup=10 argspec=PQ python=m14_workholding_select_grip

Add the indexed action and grip-selection executors to the shared __init__ function in toplevel.py, before component.ready():

self.workholding = HalExecutor(self, "workholding.0.action", component)
self.workholding_grips = [HalExecutor(self, "workholding.0.grip", component)]

Add the operations to the shared remap.py module:

from math import isfinite
from interpreter import INTERP_ERROR
def m10_workholding_unclamp(self, **words):
yield from executor_execute(self, self.workholding, request=0)
def m11_workholding_clamp(self, **words):
yield from executor_execute(self, self.workholding, request=1)
def m14_workholding_select_grip(self, **words):
device = words["p"]
mode = words["q"]
if (
not isfinite(device)
or device != int(device)
or not 0 <= device < len(self.workholding_grips)
):
self.set_errormsg("M14 requires a configured workholding device index in P")
yield INTERP_ERROR
return
if mode not in (0, 1):
self.set_errormsg("M14 requires Q0 for external or Q1 for internal grip")
yield INTERP_ERROR
return
yield from executor_execute(
self, self.workholding_grips[int(device)], request=int(mode)
)

Wire remap.executor.workholding.0.action.* to the machine-owned clamp controller. Wire each remap.executor.workholding.0.grip.* request output to the matching ctrl.executor.workholding.0.grip. input, and each ctrl.executor.workholding.0.grip. response output to its matching remap input. Ctrl acknowledges M14 after it has changed the grip mode and updated the indexed output pin.

For another device, append its executor to self.workholding_grips before component.ready(), such as HalExecutor(self, "workholding.1.grip", component), and wire its indexed pins. Keep the list in device-index order and consistent with AUTOMATIC_WORKHOLDING_DEVICES. The same M14 remap selects the endpoint using P.