Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bea77ec
CAM: Implement radial/spiral contour operation
jeffc May 29, 2026
697b249
CAM: Fix perimeter clipping bug in radial contouring
jeffc May 29, 2026
a209804
CAM: Add stock clipping intersection and perimeter expansion to radia…
jeffc May 29, 2026
92d4bf3
Implement Perimeter concentric loops option for CAM Radial contouring…
jeffc May 29, 2026
7e78416
feat(cam): add 'ignore through' option and point pruning to contour o…
jeffc May 29, 2026
cd0948a
refactor(cam): rename perimeter shape mode to concentric for radial c…
jeffc May 29, 2026
4a92e37
fix bug where open contours were being marked as closed, thus cutting
jeffc May 29, 2026
9828bfc
fix unnecessary retractions
jeffc May 29, 2026
b9ee3dc
fixed remaining bugs
jeffc May 30, 2026
23f3e7b
added docs
jeffc May 30, 2026
1353908
add docs
jeffc May 30, 2026
6ff78b3
more cleanup
jeffc May 30, 2026
82cf7b4
fix hole-skipping bug
jeffc May 30, 2026
2edd771
add initial GPU support for radial toolpaths
jeffc May 30, 2026
df26e9d
optimize hole skipping logic
jeffc Jun 1, 2026
b020090
checkpoint
jeffc Jun 3, 2026
f6ede73
siplify hole-skipping logic
jeffc Jun 3, 2026
1e50059
remove archemedian spiral
jeffc Jun 3, 2026
4e0530e
standardize on "concentric" ("offset" was used in some places to mean
jeffc Jun 3, 2026
7b73887
Update documentation for new spiralizing ops
jeffc Jun 3, 2026
22b0a8e
update docs and fix a bug introduced in roughing
jeffc Jun 3, 2026
0985772
final cleanups
jeffc Jun 3, 2026
e21aacf
final docs updates
jeffc Jun 3, 2026
add93c1
fix a bug where multi-pass spiral contours (roughing, area, level, and
jeffc Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 77 additions & 18 deletions docs/kiri-moto/CAM/ops.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Ops—not to be confused with [opps](https://knowyourmeme.com/memes/opp-opps)—
applied to a model to remove material. The Operations tab also contains meta-operations
that represent parts of the manufacturing process but do not necessarily generate toolpaths.

Each operation has parameters which can be hovered over to reveal their description.
Each operation has parameters which can be hovered over to reveal their description.
![](/img/CAM/paramDetails.png)

When an operation's settings are changed, they update the defaults for that operation
Expand All @@ -30,6 +30,7 @@ These operations include:
- [Drill](#drill)
- [Trace](#trace)
- [Pocket](#pocket)
- [Area](#area)
- [Gcode](#gcode)
- Laser Operations
- Indexed Operations
Expand Down Expand Up @@ -58,6 +59,13 @@ The Level op creates a flat-surface clearing toolpath across a selected area. It
- Creating uniform faces on complex objects
- Clearing large areas with consistent depth quickly

#### Key Options

- **Pattern**: The toolpath pattern used to surface the area:
- **linear**: Parallel zig-zag raster paths.
- **concentric**: Progressive concentric loops offsetting inwards from the perimeter.
- **spiral**: Continuously spiralized concentric offset paths.

### Rough

<ImageCarousel base="/img/CAM/example/rough/" images="rough" />
Expand All @@ -70,36 +78,55 @@ The Rough op removes large volumes of material quickly using a stepped-down tool
- Preparing a part for finish machining
- Preserving tool life by reducing cutting load in later passes

#### Key Options

- **Pattern**: The clearing pattern type:
- **concentric**: Progressive concentric loops offsetting inwards from the boundary.
- **spiral**: Continuously spiralized concentric offset paths.

### Contour

<ImageCarousel base="/img/CAM/example/contour/" images="contour" />

The Contour op generates detailed toolpaths for complex organic surface geometries.
It can trace along the X or Y axis and has configurable precision. It is useful for:
The Contour op generates detailed finishing toolpaths for complex organic surface geometries.
It can trace along the X or Y axes, or in **Radial** mode, with configurable precision. It is useful for:

- Carving 3D shapes with organic or curved profiles
- Carving 3D shapes with organic, curved, or spherical profiles
- Finishing complex wall geometries
- Cleaning up a part after [roughing](#rough)

#### Axis Modes

- **X / Y Axis**: Generates linear parallel scanning toolpaths along the selected horizontal axis.
- **Radial**: Generates toolpaths radiating from or circling around the center of the part. This has two shape submodes:
- **Spiral**: Emits a continuous spiral toolpath expanding outwards from the center of the part.
- **Concentric**: Generates concentric circular loops from the innermost boundary to the perimeter.

#### Key Options

- **Curves Only**: Restricts linear cleanup to sloped/curved surfaces, automatically skipping flat horizontal sections.
- **Curve Join Dist**: A multiplier of the tool diameter (defaults to 0.5) defining the maximum length of flat regions between curved profiles that should be bridged and kept continuous to prevent unnecessary tool retractions.
- **Omit Through**: Bypasses machining slots, holes, and other features that go completely through the part.

### Register

<ImageCarousel base="/img/CAM/example/register/" images="register" />

The Register operation drills holes in the sides of a part,
helping keep the part in the same place when it is flipped onto its opposite face.
The Register operation drills holes in the sides of a part,
helping keep the part in the same place when it is flipped onto its opposite face.
The operation has options to drill on different sides and can even generate a puzzle-piece-like pattern for registration.

## Specific Operations

Specific operations require selection of individual part features to apply operations to.
Specific operations require selection of individual part features to apply operations to.
These may overlap with Global operations but are generally more configurable.

### Drill

<ImageCarousel base="/img/CAM/example/drill/" images="drill" />

The Drill op creates pecking toolpaths for a drill tool to follow.
It can also drill holes of other diameters and even mark holes instead of drilling.
The Drill op creates pecking toolpaths for a drill tool to follow.
It can also drill holes of other diameters and even mark holes instead of drilling.
It is useful for:

- Generating a toolpath for a drill tool
Expand All @@ -110,10 +137,10 @@ It is useful for:

<ImageCarousel base="/img/CAM/example/trace/" images="trace" />

The Trace operation is likely the most configurable, allowing for generation of toolpaths following loops or lines.
It can trace on, inside, or outside a line and can take multiple passes to step down to the desired position.
When the type parameter is set to "clear," it can even act like a [Pocket](#pocket),
clearing the area above the shape created by the selected lines or loop.
The Trace operation is likely the most configurable, allowing for generation of toolpaths following loops or lines.
It can trace on, inside, or outside a line and can take multiple passes to step down to the desired position.
When the type parameter is set to "clear," it can even act like a [Pocket](#pocket),
clearing the area above the shape created by the selected lines or loop.
While the Trace op can do a lot, some specific use cases include:

- Engraving lettering or other text
Expand All @@ -125,21 +152,53 @@ While the Trace op can do a lot, some specific use cases include:

<ImageCarousel base="/img/CAM/example/pocket/" images="pocket" />

The Pocket operation takes a selection of polygon faces and generates a pocket toolpath that cuts down to them.
The operation has options to expand and smooth the pocket selection, and the contour option even allows
The Pocket operation takes a selection of polygon faces and generates a pocket toolpath that cuts down to them.
The operation has options to expand and smooth the pocket selection, and the contour option even allows
for an approximation of a v-bit carve. Some use cases specific to the Pocket op include:

- Creating one pocket in a part with multiple
- Attempting a v-carve
- Clearing a specific area of a part

#### Key Options

- **Contour / Surfacing Pattern**: When **contour** is enabled, chooses the surfacing pattern type:
- **linear**: Parallel zig-zag toolpaths.
- **concentric**: Progressive concentric loops offsetting inwards.
- **spiral**: Continuously spiralized concentric offset paths from the center outwards, with the innermost loop fully traced in its entirety to ensure no center stock is left uncut.
- **Outline Only**: Restricts pocket contouring to only trace the outer perimeter and hole outlines.
- **Omit Through**: When **Outline Only** is checked, this bypasses machining boundaries for holes and features that go completely through the part.

### Area

<ImageCarousel base="/img/CAM/example/area/" images="area" />

The Area operation allows you to select specific boundary loops or surfaces on a part to clear, trace, or surface. It provides precise control over localized features. It is useful for:

- Clearing flat pockets or horizontal areas.
- Tracing specific boundary contours.
- Surfacing localized regions of a part.

#### Key Options

- **Mode**: Chooses the operation style:
- **clear**: Clears the material inside the selected boundaries.
- **trace**: Traces the selected boundary lines directly.
- **surface**: Surfaces the area using a designated pattern.
- **Pattern**: Defines the toolpath pattern:
- **linear**: Parallel scanlines/zig-zag paths (available in **surface** mode only).
- **concentric**: Progressive concentric loops offsetting inwards from the boundary.
- **spiral**: Continuously spiralized concentric offset paths.
- **Outline Only**: When in **clear** or **surface** mode, restricts paths to trace the outer perimeter and hole outlines.
- **Omit Through**: When **Outline Only** is checked, this prunes toolpath points inside through-holes to skip them cleanly.

### Gcode

<ImageCarousel base="/img/CAM/example/gcode/" images="gcode" />

The Gcode operation does not generate a toolpath but instead adds a line or lines of G-code to the output
of the file. The Gcode operation is different from [Gcode Macros](/kiri-moto/gcode-macros),
as it is not tied to any event
The Gcode operation does not generate a toolpath but instead adds a line or lines of G-code to the output
of the file. The Gcode operation is different from [Gcode Macros](/kiri-moto/gcode-macros),
as it is not tied to any event
but is output in the order it is included in the operations array.

## Laser Operations
Expand Down
8 changes: 6 additions & 2 deletions docs/src/components/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ const imageDescripts = {
* @returns {ReactElement} - A carousel of the given images.
*/
export function ImageCarousel({ base, images, sml }) {
const list = imageDescripts[images];
if (!list || list.length === 0) {
return null;
}
return (
<div className={sml ? "carouselWrapper" : ""}>
<Carousel showThumbs={false}>
{imageDescripts[images].map(([image, caption], index) => (
<div>
{list.map(([image, caption], index) => (
<div key={index}>
<img src={base + image} alt="" />
<p className="caption">{caption}</p>
</div>
Expand Down
158 changes: 158 additions & 0 deletions src/geo/polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,152 @@ export class Polygon {
};
}

/**
* find the closest intersection on the boundary along the horizontal line Y = pt.y
*
* @param {Point} target
* @return {Point} closestPoint
*/
snapToIntersectionX(target) {
let points = this.points;
let len = points.length;
if (len === 0) return null;
if (len === 1) return points[0];
let targetY = target.y;
let targetX = target.x;
let minD = Infinity;
let closest = null;
for (let i = 0; i < len; i++) {
let p1 = points[i];
let p2 = points[this.open ? i + 1 : (i + 1) % len];
if (!p2) continue;

// Check if segment p1-p2 spans targetY
let miny = Math.min(p1.y, p2.y);
let maxy = Math.max(p1.y, p2.y);
if (targetY >= miny && targetY <= maxy) {
let x;
if (p1.y === p2.y) {
// Segment is horizontal and on targetY
x = Math.max(Math.min(p1.x, p2.x), Math.min(Math.max(p1.x, p2.x), targetX));
} else {
x = p1.x + (targetY - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
}
let d = Math.abs(targetX - x);
if (d < minD) {
minD = d;
closest = newPoint(x, targetY, target.z);
}
}
}
return closest;
}

/**
* find the closest intersection on the boundary along the vertical line X = pt.x
*
* @param {Point} target
* @return {Point} closestPoint
*/
snapToIntersectionY(target) {
let points = this.points;
let len = points.length;
if (len === 0) return null;
if (len === 1) return points[0];
let targetY = target.y;
let targetX = target.x;
let minD = Infinity;
let closest = null;
for (let i = 0; i < len; i++) {
let p1 = points[i];
let p2 = points[this.open ? i + 1 : (i + 1) % len];
if (!p2) continue;

// Check if segment p1-p2 spans targetX
let minx = Math.min(p1.x, p2.x);
let maxx = Math.max(p1.x, p2.x);
if (targetX >= minx && targetX <= maxx) {
let y;
if (p1.x === p2.x) {
// Segment is vertical and on targetX
y = Math.max(Math.min(p1.y, p2.y), Math.min(Math.max(p1.y, p2.y), targetY));
} else {
y = p1.y + (targetX - p1.x) * (p2.y - p1.y) / (p2.x - p1.x);
}
let d = Math.abs(targetY - y);
if (d < minD) {
minD = d;
closest = newPoint(targetX, y, target.z);
}
}
}
return closest;
}

snapToIntersectionAngle(target, angle) {
let dx_line = Math.cos(angle);
let dy_line = Math.sin(angle);
let points = this.points;
let len = points.length;
if (len === 0) return null;
if (len === 1) return points[0];

let closestPt = null;
let minDist = Infinity;

for (let i = 0; i < len; i++) {
let A = points[i];
let B = points[this.open ? i + 1 : (i + 1) % len];
if (!B) continue;

let dx_seg = B.x - A.x;
let dy_seg = B.y - A.y;

let det = dy_line * dx_seg - dx_line * dy_seg;
if (Math.abs(det) < 1e-9) continue; // parallel

let s = (dx_line * (A.y - target.y) - dy_line * (A.x - target.x)) / det;
if (s >= 0 && s <= 1) {
let ix = A.x + s * dx_seg;
let iy = A.y + s * dy_seg;
let ipt = newPoint(ix, iy, target.z);
let dist = target.distTo2D(ipt);
if (dist < minDist) {
minDist = dist;
closestPt = ipt;
}
}
}
return closestPt;
}

/**
* find the closest point on the polygon perimeter/boundary to target
*
* @param {Point} target
* @return {Point} closestPoint
*/
findClosestPointOnPerimeter(target) {
let points = this.points;
let len = points.length;
if (len === 0) return null;
if (len === 1) return points[0];
let minD = Infinity;
let closest = null;
for (let i = 0; i < len; i++) {
let p1 = points[i];
let p2 = points[this.open ? i + 1 : (i + 1) % len];
if (!p2) continue;
let cp = closestPointOnSegment(target, p1, p2);
let d = target.distTo2D(cp);
if (d < minD) {
minD = d;
closest = cp;
}
}
return closest;
}

/**
* @param {Polygon[]} out
* @param {[]} deep recurse and track recursion
Expand Down Expand Up @@ -1754,3 +1900,15 @@ export function fromClipperPath(path, z) {
export function newPolygon(points) {
return new Polygon(points);
}

function closestPointOnSegment(p, a, b) {
let abx = b.x - a.x;
let aby = b.y - a.y;
let apx = p.x - a.x;
let apy = p.y - a.y;
let ab2 = abx * abx + aby * aby;
if (ab2 === 0) return a;
let t = (apx * abx + apy * aby) / ab2;
t = Math.max(0, Math.min(1, t));
return newPoint(a.x + t * abx, a.y + t * aby);
}
Loading