From 7a1178166a03cc31fc5dd66b92a05fac6c7f492f Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Sun, 14 Dec 2025 16:44:56 +0100 Subject: [PATCH] fix: Combine COLOR_END and CUT flags on same stitch for color changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The COLOR_END and CUT commands must be on the same stitch, not separate stitches. This was causing the machine to execute an extra stitch with the new color before the jump to the new position. Changes: - Combine COLOR_END (X flag) and CUT (Y flag) into single stitch at old position - Machine now correctly pauses after cut, before jumping to new color section - Update all color change tests to expect combined COLOR_END+CUT stitch The correct sequence is now: 1. Finishing lock stitches (old color) 2. COLOR_END+CUT stitch (old color) ← Machine pauses here 3. Jump to new position (new color) 4. Starting lock stitches (new color) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/formats/pen/encoder.test.ts | 55 ++++++++++++--------------------- src/formats/pen/encoder.ts | 44 ++++++++++++-------------- 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/src/formats/pen/encoder.test.ts b/src/formats/pen/encoder.test.ts index 21493f9..189be15 100644 --- a/src/formats/pen/encoder.test.ts +++ b/src/formats/pen/encoder.test.ts @@ -298,21 +298,15 @@ describe('encodeStitchesToPen', () => { idx++; } - // 4. Cut command - const cutStitch = decoded[idx]; - expect(cutStitch.x).toBe(10); - expect(cutStitch.y).toBe(0); - expect(cutStitch.isCut).toBe(true); + // 4. COLOR_END + CUT command (combined on same stitch) + const colorEndCutStitch = decoded[idx]; + expect(colorEndCutStitch.x).toBe(10); + expect(colorEndCutStitch.y).toBe(0); + expect(colorEndCutStitch.isCut).toBe(true); + expect(colorEndCutStitch.isColorEnd).toBe(true); idx++; - // 5. COLOR_END marker (no jump needed since same position) - const colorEndStitch = decoded[idx]; - expect(colorEndStitch.x).toBe(10); - expect(colorEndStitch.y).toBe(0); - expect(colorEndStitch.isColorEnd).toBe(true); - idx++; - - // 6. 8 starting lock stitches for new color + // 5. 8 starting lock stitches for new color for (let i = 0; i < 8; i++) { const lockStitch = decoded[idx]; expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); @@ -320,7 +314,7 @@ describe('encodeStitchesToPen', () => { idx++; } - // 7. First stitch of new color + // 6. First stitch of new color expect(decoded[idx].x).toBe(10); expect(decoded[idx].y).toBe(0); idx++; @@ -353,9 +347,11 @@ describe('encodeStitchesToPen', () => { idx++; } - // 2. Cut command at (10, 0) - expect(decoded[idx].isCut).toBe(true); + // 2. COLOR_END + CUT command at (10, 0) - combined on same stitch expect(decoded[idx].x).toBe(10); + expect(decoded[idx].y).toBe(0); + expect(decoded[idx].isCut).toBe(true); + expect(decoded[idx].isColorEnd).toBe(true); idx++; // 3. Jump to new position (30, 10) @@ -364,20 +360,14 @@ describe('encodeStitchesToPen', () => { expect(decoded[idx].isFeed).toBe(true); idx++; - // 4. COLOR_END marker at (30, 10) - expect(decoded[idx].x).toBe(30); - expect(decoded[idx].y).toBe(10); - expect(decoded[idx].isColorEnd).toBe(true); - idx++; - - // 5. 8 starting lock stitches at (30, 10) + // 4. 8 starting lock stitches at (30, 10) for (let i = 0; i < 8; i++) { expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); idx++; } - // 6. Continue with new color stitches + // 5. Continue with new color stitches expect(decoded[idx].x).toBe(30); expect(decoded[idx].y).toBe(10); }); @@ -421,10 +411,11 @@ describe('encodeStitchesToPen', () => { idx++; } - // 4. Cut command at (10, 0) + // 4. COLOR_END + CUT command at (10, 0) - combined on same stitch expect(decoded[idx].x).toBe(10); expect(decoded[idx].y).toBe(0); expect(decoded[idx].isCut).toBe(true); + expect(decoded[idx].isColorEnd).toBe(true); idx++; // 5. Jump to new location (50, 20) - extracted from the MOVE stitch @@ -433,26 +424,20 @@ describe('encodeStitchesToPen', () => { expect(decoded[idx].isFeed).toBe(true); idx++; - // 6. COLOR_END marker at (50, 20) - expect(decoded[idx].x).toBe(50); - expect(decoded[idx].y).toBe(20); - expect(decoded[idx].isColorEnd).toBe(true); - idx++; - - // 7. 8 starting lock stitches at (50, 20) + // 6. 8 starting lock stitches at (50, 20) for (let i = 0; i < 8; i++) { expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE); idx++; } - // 8. First actual stitch of new color at (50, 20) + // 7. First actual stitch of new color at (50, 20) expect(decoded[idx].x).toBe(50); expect(decoded[idx].y).toBe(20); expect(decoded[idx].isFeed).toBe(false); idx++; - // 9. Last stitch with DATA_END + // 8. Last stitch with DATA_END expect(decoded[idx].x).toBe(60); expect(decoded[idx].y).toBe(20); expect(decoded[idx].isDataEnd).toBe(true); @@ -666,7 +651,7 @@ describe('encodeStitchesToPen', () => { const result = encodeStitchesToPen(stitches); const decoded = decodeAllPenStitches(result.penBytes); - console.log(decoded); + // Expected sequence: // 0. Feed move to proper location (should always happen here) // 1. 8 starting lock stitches at (10, 20) diff --git a/src/formats/pen/encoder.ts b/src/formats/pen/encoder.ts index 122ed8d..538cf62 100644 --- a/src/formats/pen/encoder.ts +++ b/src/formats/pen/encoder.ts @@ -17,7 +17,7 @@ const PEN_DATA_END = 0x05; // Last stitch of entire pattern const FEED_LENGTH = 50; // Long jump threshold requiring lock stitches and cut const TARGET_LENGTH = 8.0; // Target accumulated length for lock stitch direction const MAX_POINTS = 5; // Maximum points to accumulate for lock stitch direction -export const LOCK_STITCH_JUMP_SIZE = 4.0; +export const LOCK_STITCH_JUMP_SIZE = 2.0; const LOCK_STITCH_SCALE = LOCK_STITCH_JUMP_SIZE / 8.0; // Scale the magnitude-8 vector down to 4 export interface StitchData { @@ -286,7 +286,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult { penStitches.push(...generateLockStitches(absX, absY, startDir.dirX, startDir.dirY)); } - // Handle color change: finishing lock, cut, jump, COLOR_END, starting lock + // Handle color change: finishing lock, COLOR_END+CUT, jump, starting lock if (isColorChange) { const nextStitchCmd = nextStitch[2]; const nextStitchX = Math.round(nextStitch[0]); @@ -299,17 +299,26 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult { const finishDir = calculateLockDirection(stitches, i, true); penStitches.push(...generateLockStitches(absX, absY, finishDir.dirX, finishDir.dirY)); - // Step 2: Add cut command at current position - const cutXEncoded = (absX << 3) & 0xffff; - const cutYEncoded = ((absY << 3) & 0xffff) | PEN_CUT_DATA; + // Step 2: Add COLOR_END + CUT command at CURRENT position (same stitch!) + // This is where the machine pauses and waits for the user to change thread color + // IMPORTANT: COLOR_END and CUT must be on the SAME stitch, not separate stitches + let colorEndCutXEncoded = (absX << 3) & 0xffff; + let colorEndCutYEncoded = (absY << 3) & 0xffff; + + // Add COLOR_END flag to X coordinate and CUT flag to Y coordinate + colorEndCutXEncoded = (colorEndCutXEncoded & 0xfff8) | PEN_COLOR_END; + colorEndCutYEncoded |= PEN_CUT_DATA; penStitches.push( - cutXEncoded & 0xff, - (cutXEncoded >> 8) & 0xff, - cutYEncoded & 0xff, - (cutYEncoded >> 8) & 0xff + colorEndCutXEncoded & 0xff, + (colorEndCutXEncoded >> 8) & 0xff, + colorEndCutYEncoded & 0xff, + (colorEndCutYEncoded >> 8) & 0xff ); + // Machine pauses here for color change + // After user changes color, the following stitches execute with the new color + // Step 3: If next stitch is a JUMP, encode it and skip it in the loop // Otherwise, add a jump ourselves if positions differ const jumpToX = nextStitchX; @@ -334,22 +343,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult { ); } - // Step 4: Add COLOR_END marker at NEW position - // This is where the machine pauses and waits for the user to change thread color - let colorEndXEncoded = (jumpToX << 3) & 0xffff; - const colorEndYEncoded = (jumpToY << 3) & 0xffff; - - // Add COLOR_END flag to X coordinate - colorEndXEncoded = (colorEndXEncoded & 0xfff8) | PEN_COLOR_END; - - penStitches.push( - colorEndXEncoded & 0xff, - (colorEndXEncoded >> 8) & 0xff, - colorEndYEncoded & 0xff, - (colorEndYEncoded >> 8) & 0xff - ); - - // Step 5: Add starting lock stitches at the new position + // Step 4: Add starting lock stitches at the new position // Loop A: Jump/Entry Vector - Look FORWARD at upcoming stitches in new color // This hides the knot under the stitches we're about to make const nextStitchIdx = nextIsJump ? i + 2 : i + 1;