Feature Request - Specified Diameter Option for Circular & Thread Milling Operations

Feature Request - Specified Diameter Option for Circular & Thread Milling Operations

josh.millerRQWRT
Contributor Contributor
377 Views
4 Replies
Message 1 of 5

Feature Request - Specified Diameter Option for Circular & Thread Milling Operations

josh.millerRQWRT
Contributor
Contributor

I recently decided to explore some of the additional cycles within the drilling operation. Two that caught my attention were pocket milling and thread milling cycles.

What I like about these cycles compared to the traditional circular or thread milling operations is that they ignore the size of the hole in the model. 

I have been building out a file that contains templated tool paths for drilling, tapping, and thread milling operations. I am slowly switching over to TSC drills and form taps. After tapping I need to come back and spot or chamfer the top of the hole. If the major diameter of the tapped hole is bigger than the chamfer tool (I setup a 3/8" spot drill as a chamfer mill), I can very quickly have it chamfer the hole .010" larger than the major diameter without having to investigate what size the hole is in the model.

The same situation applies to thread milling. Instead of having to investigate the diameter of the hole in the model, this  cycle will go to specified diameter.

What's not great about these cycles is that they do not allow for compensation, lead in's / lead out's, multiple passes, a finish pass, or stock to leave.

Alternatively, instead of adding options to these cycles, I think it could be helpful to add an option to the circular and thread milling operations to go to specified diameter / ignore the hole size in the model. This would be a nice quality of life improvement / way to "automate" part of the CAM workflow.

0 Likes
378 Views
4 Replies
Replies (4)
Message 2 of 5

programming2C78B
Mentor
Mentor

I've been asking for this for atleast 2 years 🙂

 

image.png

"What's not great about these cycles is that they do not allow for compensation, lead in's / lead out's, multiple passes, a finish pass, or stock to leave."

is this a typo?

Please click "Accept Solution" if what I wrote solved your issue!
0 Likes
Message 3 of 5

josh.millerRQWRT
Contributor
Contributor

The cycles contained within the drilling operation do not allow for compensation or lead in's / lead out's. The thread milling operation does not allow for multiple passes or stock to leave. Neither cycle allows for a finishing passes (nor do the circular or thread operations). 

 

Drilling - Pocket Milling compared with Circular Operation   

Drill - Pocket Mill.jpg

Circular - Passes.jpg

Circular - Linking.jpg

 

 

Drilling - Thread Milling compared with Thread Operation

Drill - Thread Mill.jpg

Thread Mill - Passes.jpg

  

Thread Mill - Linking.jpg

   

 

 

 

 

 

0 Likes
Message 4 of 5

programming2C78B
Mentor
Mentor

Thanks for clarifying. I only ever do Thread mill from its own dedicated strategy. 

Please click "Accept Solution" if what I wrote solved your issue!
0 Likes
Message 5 of 5

josh.millerRQWRT
Contributor
Contributor

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.

0 Likes