Skip to content

Commit 31cb46f

Browse files
Fix milling safety blocks and iTNC execution
1 parent 1857b5d commit 31cb46f

16 files changed

Lines changed: 278 additions & 37 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 2.8.1 - 2026-10-18
4+
5+
- Unify built-in rotary profile names as 4 Ax / 5 Ax Head / Table while preserving profile IDs and geometry.
6+
- Accept spaces in unquoted HEIDENHAIN program names and support M128/TCPM with a single rotary table axis, retaining configured axis directions and existing unsupported-configuration diagnostics.
7+
- Fix milling safety blocks combining G80 with explicit G00/G01 motion: cancel active drilling cycles and execute the programmed move regardless of code order, while retaining diagnostics for conflicting motion commands.
8+
- Accept HEIDENHAIN TOOL CALL DL/DR offsets. Apply DR to cutter radius in supported pocket, bore-milling and thread-milling cycles without modifying saved tool geometry; warn that contour compensation retains the base radius and DL is ignored in the tool-tip path.
9+
- Use a 3-degree plunge angle for HEIDENHAIN pocket insertion when no explicit angle is supplied, with a warning identifying the assumption and no additional tool-library fields.
10+
- Warn when HEIDENHAIN finishing-only pocket cycles have zero side and floor allowances and therefore generate no cutting passes.
11+
312
## 2.8.0 - 2026-10-18
413

514
- Add native SINUMERIK POCKET3/POCKET4 roughing and finishing, HOLES1/HOLES2 drilling patterns, CYCLE90 thread milling, and DEF REAL/INT variables. Support documented subsets and diagnose unsupported cycle variants explicitly.

‎app/gcode/kernel/frontend/heidenhain.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def _contour(line):
108108

109109

110110
def _boundary(line):
111-
match = re.fullmatch(r'(BEGIN|END)\s+PGM\s+("(?:[^"\n]|"")+"|[^\s"]+)(?:\s+(MM|INCH))?', line.body, re.I)
111+
match = re.fullmatch(r'(BEGIN|END)\s+PGM\s+("(?:[^"\n]|"")+"|[^"\n]+?)(?:\s+(MM|INCH))?', line.body, re.I)
112112
if match is None:
113113
raise HeidenhainParseError("Expected BEGIN/END PGM name MM/INCH", line)
114114
name = match[2]

‎app/gcode/kernel/machine/execution.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def modal_group_conflicts(gcodes: tuple[int | float, ...], language: str) -> tup
181181
"""Return conflicting supported modal codes grouped by machine dialect."""
182182
groups = _MILLING_MODAL_GROUPS if language == "fanuc_mill" else _TURNING_MODAL_GROUPS
183183
numeric_codes = {int(code) if float(code).is_integer() else float(code) for code in gcodes}
184+
# Cancellation may accompany an explicit ordinary move in a safety block.
185+
if language == "fanuc_mill" and numeric_codes & {0, 1, 2, 3}:
186+
numeric_codes.discard(80)
184187
conflicts = []
185188
for name, members in groups.items():
186189
present = tuple(sorted(numeric_codes & members))

‎app/gcode/kernel/milling/cycles/drilling.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def execute_milling_cycle(context: CycleContext, *, emit_geometry: bool = True)
316316
trial_state = replace(state)
317317
apply_cycle_outcome(trial_state, CycleOutcome(modal_updates=modal_updates))
318318

319-
explicit_cycle = bool(cycle_codes)
319+
explicit_cycle = bool(cycle_codes) and not (80 in cycle_codes and any(g in context.codes for g in (0, 1, 2, 3)))
320320
should_expand = emit_geometry and trial_state.cycle != 80 and has_position
321321
handled = emit_geometry and (explicit_cycle or should_expand)
322322
if not should_expand:

‎app/gcode/kernel/milling/heidenhain.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ class _State:
8080
spindle_direction: int | None = None
8181
center: tuple[float, float, float] | None = None
8282
tolerance_warned: bool = False
83+
plunge_angle_warned: bool = False
84+
tool_radius_delta: float = 0.0
8385
arc_tolerance: float = 0.01
8486
ended: str | None = None
8587
compensation_mode: int = 40
@@ -139,6 +141,8 @@ def controls(self, syntax, values, block):
139141
"IB",
140142
"IC",
141143
}
144+
if syntax.command == "TOOL CALL":
145+
unsupported -= {"DL", "DR"}
142146
if unsupported:
143147
_unsupported("Unsupported Klartext addresses: " + ", ".join(sorted(unsupported)))
144148
self.feed_controls(syntax, values, block)
@@ -491,12 +495,39 @@ def tool_call(self, syntax, block):
491495
ExecutionEvent("tool_change", block.index, "TOOL CALL", tool=tool, previous_tool=self.tool)
492496
)
493497
self.tool = tool
498+
values = {
499+
word.address: scalar(word.expression, self.parameters)
500+
for word in syntax.positioning_words
501+
if word.address in {"DL", "DR"}
502+
}
503+
self.tool_radius_delta = values.get("DR", 0.0) * self.scale
504+
if self.tool_radius_delta:
505+
self.warning(
506+
"HEIDENHAIN_TOOL_RADIUS_DELTA",
507+
"TOOL CALL DR is applied to supported cycle geometry; contour compensation uses the base tool radius",
508+
block,
509+
)
510+
if values.get("DL", 0.0):
511+
self.warning("HEIDENHAIN_TOOL_LENGTH_DELTA_IGNORED", "TOOL CALL DL is ignored in the tool-tip path", block)
494512
if syntax.spindle_axis:
495513
self.tool_axis_declared = True
496514
self.spindle_axis = syntax.spindle_axis
497515
self.plane = _PLANES[self.spindle_axis][0]
498516
self.center = None
499517

518+
def warning(self, code, message, block):
519+
self.diagnostics.append(Diagnostic(code, message, "warning", "unverified", block.index + 1, block.raw))
520+
521+
def cycle_tool(self):
522+
"""Apply the current call's radial delta without changing the tool library."""
523+
tool = self.tools.get(self.tool)
524+
if not tool or not self.tool_radius_delta:
525+
return tool
526+
diameter = float(tool.get("diameter", 0.0)) + 2 * self.tool_radius_delta
527+
if not math.isfinite(diameter) or diameter <= 0:
528+
raise SemanticError("INVALID_TOOL", "TOOL CALL DR must leave a positive effective cutter diameter")
529+
return {**tool, "diameter": diameter}
530+
500531
def cycle(self, syntax, values, block):
501532
if syntax.cycle_code in {7, 10} and syntax.command == "CYCL DEF":
502533
self.coordinate_cycle(syntax, values, block)

‎app/gcode/kernel/milling/heidenhain_cycles.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def back_boring(self):
145145
def bore_milling(self, surface, depth, safety, returned, feed):
146146
axis = self.axis
147147
plane, axes = {2: (17, (0, 1)), 1: (18, (0, 2)), 0: (19, (1, 2))}[axis]
148-
tool = self.state.tools.get(self.state.tool, {})
148+
tool = self.state.cycle_tool() or {}
149149
diameter = self.q(335) * self.state.scale
150150
tool_diameter = float(tool.get("diameter", 0))
151151
radius = (diameter - tool_diameter) / 2

‎app/gcode/kernel/milling/heidenhain_pockets.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def _plan(state, block):
151151
if q["Q201"] > 0:
152152
_error("Positive depth depends on the OEM MP7441 policy", unsupported=True)
153153
_check_parameters(q)
154-
tool = _tool_radius(state.tools.get(state.tool))
154+
tool = _tool_radius(state.cycle_tool())
155155
if tool is None:
156156
_error("Supply a milling cutter diameter through milling_tools/tool_resolver", unsupported=True)
157157
if state.spindle_direction not in {3, 4}:
@@ -163,7 +163,14 @@ def _plan(state, block):
163163

164164
def _plunge_angle(state):
165165
tool = state.tools.get(state.tool, {})
166-
value = tool.get("ANGLE", tool.get("plunge_angle"))
166+
value = tool.get("ANGLE", tool.get("plunge_angle", 3.0))
167+
if "ANGLE" not in tool and "plunge_angle" not in tool and not state.plunge_angle_warned:
168+
state.warning(
169+
"HEIDENHAIN_DEFAULT_PLUNGE_ANGLE",
170+
"Pocket insertion uses an assumed plunge angle of 3 degrees",
171+
state.program.blocks[state.pc],
172+
)
173+
state.plunge_angle_warned = True
167174
try:
168175
angle = float(value)
169176
except (TypeError, ValueError):
@@ -231,6 +238,11 @@ def execute_pocket(state, block):
231238
if state.parameters.get("Q201") == 0:
232239
return
233240
if state.parameters.get("Q215") == 2 and state.parameters.get("Q368") == state.parameters.get("Q369") == 0:
241+
state.warning(
242+
"HEIDENHAIN_POCKET_NO_FINISHING_ALLOWANCE",
243+
"Finishing-only pocket has Q368=Q369=0; no cutting passes are generated",
244+
block,
245+
)
234246
return
235247
pocket = _plan(state, block)
236248
q, scale = pocket.q, state.scale

‎app/gcode/kernel/milling/heidenhain_rotary.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,16 @@ def _capture(state):
5757
def set_tcp(state, enabled, block):
5858
if enabled:
5959
profile = _require_profile(state)
60-
if not profile.tcp_table_supported or state.rotary.plane is not None or state.spindle_axis != "Z":
60+
single_table = not profile.head_rotary_axes and len(profile.table_rotary_axes) == 1
61+
if (
62+
not (profile.tcp_table_supported or single_table)
63+
or state.rotary.plane is not None
64+
or state.spindle_axis != "Z"
65+
):
6166
raise SemanticError(
62-
"UNSUPPORTED_HEIDENHAIN_TCP", "M128/TCPM requires an AC/BC table and PLANE RESET", "unsupported"
67+
"UNSUPPORTED_HEIDENHAIN_TCP",
68+
"M128/TCPM requires a single rotary table or an AC/BC table, Z tool axis and PLANE RESET",
69+
"unsupported",
6370
)
6471
if state.rotary.tcp == enabled:
6572
return

‎app/gcode/kernel/milling/heidenhain_thread.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def thread_plan(state):
3333
if q["Q351"] not in {-1, 1} or state.spindle_direction not in {3, 4}:
3434
_unsupported("Specify Q351=+1/-1 and spindle direction M3/M4")
3535
_check_ranges(q, rapid)
36-
tool_radius = _tool_radius(state.tools.get(state.tool))
36+
tool_radius = _tool_radius(state.cycle_tool())
3737
diameter = q["Q335"] * state.scale
3838
if tool_radius is None:
3939
_unsupported("Cycle 262 requires a milling cutter diameter from milling_tools/tool_resolver")

‎app/gcode/kernel/milling/rotary_profiles.json‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"profiles": [
44
{
55
"id": "4ax_head_b_negative",
6-
"name": "4Ax Head B Negative",
6+
"name": "4 Ax Head B Negative",
77
"table": [],
88
"head": [
99
{
@@ -19,7 +19,7 @@
1919
},
2020
{
2121
"id": "4ax_table_b",
22-
"name": "4Ax Table B",
22+
"name": "4 Ax Table B",
2323
"table": [
2424
{
2525
"address": "B",
@@ -35,7 +35,7 @@
3535
},
3636
{
3737
"id": "4ax_table_a",
38-
"name": "4Ax Table A",
38+
"name": "4 Ax Table A",
3939
"table": [
4040
{
4141
"address": "A",
@@ -51,7 +51,7 @@
5151
},
5252
{
5353
"id": "5ax_table_ac_angled",
54-
"name": "5Ax Table AC Angled",
54+
"name": "5 Ax Table AC Angled",
5555
"table": [
5656
{
5757
"address": "A",
@@ -75,7 +75,7 @@
7575
},
7676
{
7777
"id": "5ax_table_bc_angled",
78-
"name": "5Ax Table BC Angled",
78+
"name": "5 Ax Table BC Angled",
7979
"table": [
8080
{
8181
"address": "B",
@@ -99,7 +99,7 @@
9999
},
100100
{
101101
"id": "5ax_head_ac_angled",
102-
"name": "5Ax Head AC Angled",
102+
"name": "5 Ax Head AC Angled",
103103
"table": [],
104104
"head": [
105105
{
@@ -123,7 +123,7 @@
123123
},
124124
{
125125
"id": "5ax_head_bc_angled",
126-
"name": "5Ax Head BC Angled",
126+
"name": "5 Ax Head BC Angled",
127127
"table": [],
128128
"head": [
129129
{
@@ -147,7 +147,7 @@
147147
},
148148
{
149149
"id": "5ax_table_ac",
150-
"name": "5Ax Table AC",
150+
"name": "5 Ax Table AC",
151151
"table": [
152152
{
153153
"address": "A",
@@ -171,7 +171,7 @@
171171
},
172172
{
173173
"id": "5ax_table_bc",
174-
"name": "5Ax Table BC",
174+
"name": "5 Ax Table BC",
175175
"table": [
176176
{
177177
"address": "B",
@@ -195,7 +195,7 @@
195195
},
196196
{
197197
"id": "5ax_table_c_head_b_negative",
198-
"name": "5Ax Table C Head B Negative",
198+
"name": "5 Ax Table C Head B Negative",
199199
"table": [
200200
{
201201
"address": "C",
@@ -220,7 +220,7 @@
220220
},
221221
{
222222
"id": "5ax_head_ac_negative",
223-
"name": "5Ax Head AC Negative",
223+
"name": "5 Ax Head AC Negative",
224224
"table": [],
225225
"head": [
226226
{
@@ -244,7 +244,7 @@
244244
},
245245
{
246246
"id": "5ax_head_bc_negative",
247-
"name": "5Ax Head BC Negative",
247+
"name": "5 Ax Head BC Negative",
248248
"table": [],
249249
"head": [
250250
{
@@ -268,7 +268,7 @@
268268
},
269269
{
270270
"id": "5ax_table_c_head_a_negative",
271-
"name": "5Ax Table C Head A Negative",
271+
"name": "5 Ax Table C Head A Negative",
272272
"table": [
273273
{
274274
"address": "C",
@@ -293,7 +293,7 @@
293293
},
294294
{
295295
"id": "5ax_table_a_head_b",
296-
"name": "5Ax Table A Head B",
296+
"name": "5 Ax Table A Head B",
297297
"table": [
298298
{
299299
"address": "A",
@@ -318,7 +318,7 @@
318318
},
319319
{
320320
"id": "5ax_table_ab",
321-
"name": "5Ax Table AB",
321+
"name": "5 Ax Table AB",
322322
"table": [
323323
{
324324
"address": "A",
@@ -342,7 +342,7 @@
342342
},
343343
{
344344
"id": "4ax_table_c",
345-
"name": "4-axis Table C",
345+
"name": "4 Ax Table C",
346346
"table": [
347347
{
348348
"address": "C",
@@ -358,7 +358,7 @@
358358
},
359359
{
360360
"id": "4ax_head_a",
361-
"name": "4-axis Head A",
361+
"name": "4 Ax Head A",
362362
"table": [],
363363
"head": [
364364
{
@@ -374,7 +374,7 @@
374374
},
375375
{
376376
"id": "4ax_head_b",
377-
"name": "4-axis Head B",
377+
"name": "4 Ax Head B",
378378
"table": [],
379379
"head": [
380380
{
@@ -390,7 +390,7 @@
390390
},
391391
{
392392
"id": "4ax_head_c",
393-
"name": "4-axis Head C",
393+
"name": "4 Ax Head C",
394394
"table": [],
395395
"head": [
396396
{
@@ -406,7 +406,7 @@
406406
},
407407
{
408408
"id": "4ax_table_a_negative",
409-
"name": "4-axis Table A Negative",
409+
"name": "4 Ax Table A Negative",
410410
"table": [
411411
{
412412
"address": "A",
@@ -422,7 +422,7 @@
422422
},
423423
{
424424
"id": "4ax_table_b_negative",
425-
"name": "4-axis Table B Negative",
425+
"name": "4 Ax Table B Negative",
426426
"table": [
427427
{
428428
"address": "B",
@@ -438,7 +438,7 @@
438438
},
439439
{
440440
"id": "4ax_table_c_negative",
441-
"name": "4-axis Table C Negative",
441+
"name": "4 Ax Table C Negative",
442442
"table": [
443443
{
444444
"address": "C",
@@ -454,7 +454,7 @@
454454
},
455455
{
456456
"id": "4ax_head_a_negative",
457-
"name": "4-axis Head A Negative",
457+
"name": "4 Ax Head A Negative",
458458
"table": [],
459459
"head": [
460460
{
@@ -470,7 +470,7 @@
470470
},
471471
{
472472
"id": "4ax_head_c_negative",
473-
"name": "4-axis Head C Negative",
473+
"name": "4 Ax Head C Negative",
474474
"table": [],
475475
"head": [
476476
{

0 commit comments

Comments
 (0)