I was going to try and upload a PDF detailing some of the post modification I made. For some reason I cannot attach any documents. So copying and pasting the document below. These modifications provide me with the desired output. I have only tested a few scenarios so far, so use at your own risk.
Cutter Compensation for Drill-Nested Pocket Milling & Thread Milling
=====================================================================
THE PROBLEM
-----------
Fusion's drilling operation includes nested strategies for circular pocket milling (circular-pocket-milling) and thread milling (thread-milling). These are useful because they let you specify a diameter directly — unlike the standalone Circular Milling and Thread Milling operations, which are tied to model geometry.
The problem: the drill-nested versions compute cutter compensation in CAM. The output coordinates already account for tool diameter, with no G41/G42 for the control to apply wear offsets. If your tool is slightly over/undersized or you need to dial in a bore, you have no D-offset adjustment on the control.
The standalone operations DO output G41/G42 with proper lead-in/lead-out geometry, but they don't have the "go to specified diameter" option that makes the drill-nested versions so practical.
THE SOLUTION
------------
This post modification intercepts the cycle-expanded motion events and injects G41/G40 cutter compensation with fabricated lead-in/lead-out geometry. The result matches the pattern Fusion uses for its standalone circular and thread milling operations.
Thread Milling (Helical Lead-In)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The cycle expansion includes a G01 XY move from hole center to circle start. We replace it with a helical lead-in:
1. G01 G41 DA — line to offset point (r = tool_diameter × 0.1 back from circle entry, at current Z)
2. G03 — helical quarter-arc from offset to circle entry, Z ramps up by pitch/4
The post does NOT add any extra Z plunge. The bottom height is the user's to control in CAM. The final G01 return-to-center gets G40. The exit helix is unchanged from the stock cycle expansion.
Stock output:
G01 X1.234 Y0.567 F20. (center to circle - no comp)
G03 X... Y... Z... I... J... (helical arc - 1st half)
G03 X... Y... Z... I... J... (helical arc - 2nd half)
G03 X... Y... Z... I... J... (helical arc - nth half)
G01 X0. Y0. (return to center - no comp)
Modified output:
G01 G41 X1.198 Y0.543 DA F20. (lead-in - comp on)
G03 X1.234 Y0.567 Z-0.475 I... J... (quarter-arc onto circle)
G03 X... Y... Z... I... J... (helical arc - 1st half - unchanged)
G03 X... Y... Z... I... J... (helical arc - 2nd half - unchanged)
G03 X... Y... Z... I... J... (helical arc - nth half - unchanged)
G01 G40 X0. Y0. (return to center - comp off)
Circular Pocket Milling (Fabricated Lead-In/Lead-Out)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The cycle expansion goes directly from a Z plunge into a G03 semicircle entry arc — there's no G01 XY move to activate G41 on. The post fabricates the geometry:
- Entry: Replace the semicircle entry arc with G01 G41 DA line + quarter-arc G03 onto the cutting circle
- Exit: Replace the G01 return-to-center with quarter-arc G03 off the circle + G01 G40 line to center
The lead-in arc radius is tool_diameter × 0.1 (matches Fusion's standalone default). Works correctly with multi-pass spiral-out patterns.
Stock output:
G01 Z-0.5 F10. (plunge)
G03 X1.234 Y0.567 I0.617 J0. (semicircle entry - no comp)
G03 X-1.234 Y-0.567 I... J... (circular arc - 1st half)
G03 X1.234 Y0.567 I... J... (circular arc - 2nd half)
G01 X0. Y0. (return to center - no comp)
Modified output:
G01 Z-0.5 F10. (plunge - unchanged)
G01 G41 X1.198 Y0.543 DA F20. (lead-in - comp on)
G03 X1.234 Y0.567 I... J... (quarter-arc onto circle)
G03 X-1.234 Y-0.567 I... J... (circular arc - 1st half - unchanged)
G03 X1.234 Y0.567 I... J... (circular arc - 2nd half - unchanged)
G03 X1.198 Y0.610 I... J... (quarter-arc off circle)
G01 G40 X0. Y0. F20. (lead-out - comp off)
================================================================================
IMPLEMENTATION
================================================================================
There are 6 code locations to modify. The surrounding context (function names, existing code) is included so you can find the right insertion point in your post.
1. State Variable Declaration
-----------------------------
Add this block before your `var settings = {` declaration:
// Cutter compensation injection for drill-nested cycle expansions
var drillCompState = {
active : false, // true when inside a comp-eligible cycle expansion
type : "", // "thread-milling" or "circular-pocket-milling"
entryDone : false, // true after G41 entry has been output
holeCenter : undefined, // {x, y, z} - position at start of cutting moves
circleEntryPoint: undefined, // {x, y} - point on cutting circle
circleRadius : 0, // distance from center to circle (pocket mill)
pitch : 0 // thread pitch (thread mill, from cycle.pitch)
};
2. Initialize State in onCycle()
--------------------------------
In your onCycle() function, add after the `writeBlock(gPlaneModal.format(17));` line. This activates the state machine once per cycle; per-hole fields are reset in onCyclePoint() (section 4).
// Initialize comp state for drill-nested cycles that need G41/G40.
// Activates the state machine once per cycle; per-hole fields (entryDone,
// holeCenter, etc.) are reset in onCyclePoint() for each hole.
if (cycleType == "thread-milling" || cycleType == "circular-pocket-milling") {
drillCompState.active = true;
drillCompState.type = cycleType;
drillCompState.entryDone = false;
drillCompState.holeCenter = undefined;
drillCompState.circleEntryPoint = undefined;
drillCompState.circleRadius = 0;
drillCompState.pitch = (cycleType == "thread-milling") ? cycle.pitch : 0;
}
3. Clear State in onCycleEnd()
------------------------------
Add as the first line inside onCycleEnd():
drillCompState.active = false; // clear comp state
4. Per-Hole State Reset in onCyclePoint()
-----------------------------------------
Add at the very beginning of onCyclePoint(), before the existing inspection/probe/drill dispatch. Without this reset, only the first hole in a multi-hole pattern gets the fabricated G41 entry — subsequent holes use stale state from the first hole, producing wrong exit arc geometry and orphaned G40 without a matching G41.
function onCyclePoint(x, y, z) {
// Reset per-hole comp state so every hole gets its own G41/G40.
// onCycle() activates drillCompState.active once; here we reset the
// per-hole fields so each hole's semicircle entry is intercepted and
// holeCenter is freshly captured.
if (drillCompState.active) {
drillCompState.entryDone = false;
drillCompState.holeCenter = undefined;
drillCompState.circleEntryPoint = undefined;
drillCompState.circleRadius = 0;
}
// ... rest of onCyclePoint unchanged
5. Linear Move Interception in onLinear()
-----------------------------------------
Add at the very beginning of onLinear(), before existing x/y/z formatting:
function onLinear(_x, _y, _z, feed) {
if (drillCompState.active) {
var curPos = getCurrentPosition();
var isXYMove = xyzFormat.areDifferent(_x, curPos.x) ||
xyzFormat.areDifferent(_y, curPos.y);
if (drillCompState.type == "thread-milling") {
// ENTRY: Replace flat G01 with helical lead-in
if (!drillCompState.entryDone && isXYMove) {
var Cx = curPos.x; var Cy = curPos.y;
var Ox = _x; var Oy = _y;
var R = Math.sqrt((Ox-Cx)*(Ox-Cx) + (Oy-Cy)*(Oy-Cy));
var r = tool.diameter * 0.1;
var theta = Math.atan2(Oy - Cy, Ox - Cx);
var zRamp = drillCompState.pitch / 4;
var bottomZ = curPos.z;
var leadInX = Cx + (R-r)*Math.cos(theta) + r*Math.sin(theta);
var leadInY = Cy + (R-r)*Math.sin(theta) - r*Math.cos(theta);
writeBlock(gMotionModal.format(1), gFormat.format(41),
xOutput.format(leadInX), yOutput.format(leadInY),
getToolOffsetCode("diameter"), getFeed(feed));
var arcI = -r * Math.sin(theta);
var arcJ = r * Math.cos(theta);
writeBlock(gMotionModal.format(3),
xOutput.format(Ox), yOutput.format(Oy),
zOutput.format(bottomZ + zRamp),
iOutput.format(arcI), jOutput.format(arcJ));
drillCompState.entryDone = true;
return;
}
// EXIT: G40 on return-to-center
if (drillCompState.entryDone && isXYMove) {
writeBlock(gMotionModal.format(1), gFormat.format(40),
xOutput.format(_x), yOutput.format(_y), getFeed(feed));
return;
}
}
if (drillCompState.type == "circular-pocket-milling") {
// EXIT: quarter-arc off circle + G40 line
if (drillCompState.entryDone && isXYMove) {
var Cx = drillCompState.holeCenter.x;
var Cy = drillCompState.holeCenter.y;
var Ox = curPos.x; var Oy = curPos.y;
var R = Math.sqrt((Ox-Cx)*(Ox-Cx) + (Oy-Cy)*(Oy-Cy));
var r = tool.diameter * 0.1;
var theta = Math.atan2(Oy - Cy, Ox - Cx);
var exitX = Cx + (R-r)*Math.cos(theta) - r*Math.sin(theta);
var exitY = Cy + (R-r)*Math.sin(theta) + r*Math.cos(theta);
var exitI = -r * Math.cos(theta);
var exitJ = -r * Math.sin(theta);
writeBlock(gMotionModal.format(3),
xOutput.format(exitX), yOutput.format(exitY),
iOutput.format(exitI), jOutput.format(exitJ),
getFeed(feed));
writeBlock(gMotionModal.format(1), gFormat.format(40),
xOutput.format(_x), yOutput.format(_y), getFeed(feed));
return;
}
}
}
// --- normal onLinear continues below ---
6. Circular Move Interception in onCircular()
----------------------------------------------
Add at the very beginning of onCircular(), before the pendingRadiusCompensation check:
function onCircular(clockwise, cx, cy, cz, x, y, z, feed) {
// Pocket mill entry - replace semicircle with fabricated lead-in
if (drillCompState.active &&
drillCompState.type == "circular-pocket-milling" &&
!drillCompState.entryDone) {
var curPos = getCurrentPosition();
var Cx = curPos.x; var Cy = curPos.y;
var Ox = x; var Oy = y;
var R = Math.sqrt((Ox-Cx)*(Ox-Cx) + (Oy-Cy)*(Oy-Cy));
var r = tool.diameter * 0.1;
var theta = Math.atan2(Oy - Cy, Ox - Cx);
drillCompState.holeCenter = {x:Cx, y:Cy, z:curPos.z};
drillCompState.circleEntryPoint = {x:Ox, y:Oy};
drillCompState.circleRadius = R;
var leadInX = Cx + (R-r)*Math.cos(theta) + r*Math.sin(theta);
var leadInY = Cy + (R-r)*Math.sin(theta) - r*Math.cos(theta);
var arcI = -r * Math.sin(theta);
var arcJ = r * Math.cos(theta);
writeBlock(gMotionModal.format(1), gFormat.format(41),
xOutput.format(leadInX), yOutput.format(leadInY),
getToolOffsetCode("diameter"), getFeed(feed));
writeBlock(gMotionModal.format(clockwise ? 2 : 3),
xOutput.format(Ox), yOutput.format(Oy),
iOutput.format(arcI), jOutput.format(arcJ));
drillCompState.entryDone = true;
return; // suppress original entry semicircle
}
if (pendingRadiusCompensation >= 0) {
// ... rest of onCircular unchanged
================================================================================
Optional: Allow Smoothing for Drill-Nested Milling
---------------------------------------------------
By default, the post disables smoothing for all drilling cycles. Since drill-nested pocket and thread milling expand into linear/circular moves, they benefit from smoothing. In initializeSmoothing(), replace the smoothing.isAllowed line:
var isDrillNestedMilling = hasParameter("operation:cycleType") &&
(getParameter("operation:cycleType") == "circular-pocket-milling" ||
getParameter("operation:cycleType") == "circular-pocket-milling-roughing" ||
getParameter("operation:cycleType") == "thread-milling");
smoothing.isAllowed = !(currentSection.getTool().type == TOOL_PROBE ||
(isDrillingCycle() && !isDrillNestedMilling));
================================================================================
NOTES
-----
- getToolOffsetCode("diameter") outputs the appropriate D-offset code for your control (DA, DB, D##). This function already exists in the stock Okuma post.
- Lead-in arc radius is tool.diameter * 0.1 — matches Fusion's default for standalone circular/thread milling.
- Thread milling lead-in Z ramp = pitch/4 (quarter-turn of helix). If your bottom height doesn't account for this, adjust in CAM.
- The geometry math uses atan2 to handle any entry angle — works regardless of where on the circle the cycle expansion starts.
- For multi-hole patterns, the per-hole reset in onCyclePoint() (section 4) ensures each hole gets fresh entry/exit fabrication. Without it, only the first hole receives G41 and subsequent holes produce incorrect exit arcs from stale center coordinates.
- Tested on Okuma OSP-P300A (Genos M560-V). The G41/G40 with DA offset codes and fabricated arc geometry are standard G-code — should work on any OSP control.