type WorkpiecePlacement =
{ Id : WorkpieceId
/// Corner of the workpiece we'll attach to the machine.
WorkpieceCorner : WorkpieceSpace.Corner3D
/// Point in machine-space we'll anchor this corner to.
MachinePoint : MachineSpace.Point
/// Which face of the workpiece is on top.
FaceUp : WorkpieceSpace.Face
/// Rotation around the up-axis.
Yaw : WorkpieceSpace.Yaw
}
type OperationType =
| PlaceWorkpiece of placement : Operation.WorkpiecePlacement
| InstallTool of id : ToolId * slot : int option
| ZeroAt of point : MachineSpace.Point
| RunGCode of source : GCode.Source
| RemoveWorkpiece of id : WorkpieceId
For the GCode simulator I needed a parser for GCode files, which produces a type with 1:1 equivalence to the GCode instruction set: type GCodeInstruction =
// --- Motion ---
| G0_RapidMove of axisMoves : (Axis * float<gcodeunit>) array
| G1_Move of feedRate : float<gcodeunit/minute> option * axisMoves : (Axis * float<gcodeunit>) array
| G2_ClockwiseArc of ArcParams
| G3_CounterClockwiseArc of ArcParams
| G4_Dwell of seconds : double
// --- Plane selection ---
| G17_SelectXYPlane
| G18_SelectXZPlane
| G19_SelectYZPlane
// --- Unit selection ---
| G20_Inches
| G21_Millimeters
// --- Distance mode ---
| G90_AbsoluteDistance
| G91_RelativeDistance
// ... etc truncated, more instructions in real code
But my tool supports doing transforms on toolpaths, like rotating 90 degrees or offsetting so I can easily define that I want to make tiling copies of the same project.
To implement those transforms straight up as GCodeInstruction[] -> GCodeInstruction[] is a bad call. GCode is very stateful and lets you switch units, relative vs. absolute coordinate spaces, etc. in instructions. That makes the transform awkward and tricky to write. type ToolPathInstruction =
| Rapid of From : Point * To : Point
| Linear of From : Point * To : Point * Feed : FeedRate
| Arc of
From : Point *
To : Point *
Center : Point *
Plane : Plane *
Direction : ArcDirection *
Feed : FeedRate
| ... etc truncated
That is the appropriate level for the transforms like offset, rotate, scale, etc. to operate on. type ApproxMove =
{ From : Vector3
To : Vector3
FeedRate : double<m/minute>
IsRapid : bool
}
type ToolPathApproximation =
{ StartPosition : Vector3
Moves : ApproxMove[]
}
Having defined all these types it's clear that I need operations like: parse: string -> GCode
serialize : GCode -> string
normalizeToToolPath : GCode -> ToolPath
denormalizeToGCode : ToolPath -> GCode
offset : Vector3 -> ToolPath -> ToolPath
rotate90 : ToolPath -> ToolPath
scale : Vector3 -> ToolPath -> ToolPath
approximate : ToolPath -> ToolPathApproximation
simulate : ToolPathApproximation -> MachineState -> MachineState
renderToolPathWireframe : ToolPathApproximation -> VBO
renderMachineState : MachineState -> VBO
And so on. An LLM is absolutely awesome at one-shotting the implementations.
The problem is the IDE refactor->rename updates the code but the agent's "rename" will also catch developer-facing documentation in text files, comments, etc. that referenced the old name. It will often even catch reflection code that referred to the old name in a string. And it will do the mental work of disambiguating "this reference is something else that shouldn't be updated, that one is really pointing at the thing that got renamed and should be updated". If asked to, it can catch things like "var postGresClient = new PostgreSQLClient()" and change them to "var dbClient = new DatabaseClient()".
My preconception was: the IDE feature is deterministic and works every time. The LLM may hallucinate and fail to correctly do the rename, so it's both slower and worse.
My actual experience has been: So far, I've never actually seen Opus or GPT5.5 hallucinate and fail at a simple refactoring task like this, but I have had numerous instances where it caught extra stuff that a deterministic rename never could, and therefore did the task slower and much better.
I hate it because it feels lazy and stupid to type "do this trivial thing for me" into a prompt box. But dammit, it works too well.