fix: Combine COLOR_END and CUT flags on same stitch for color changes

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 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik 2025-12-14 16:44:56 +01:00
parent 6048a61230
commit 7a1178166a
2 changed files with 39 additions and 60 deletions

View file

@ -298,21 +298,15 @@ describe('encodeStitchesToPen', () => {
idx++; idx++;
} }
// 4. Cut command // 4. COLOR_END + CUT command (combined on same stitch)
const cutStitch = decoded[idx]; const colorEndCutStitch = decoded[idx];
expect(cutStitch.x).toBe(10); expect(colorEndCutStitch.x).toBe(10);
expect(cutStitch.y).toBe(0); expect(colorEndCutStitch.y).toBe(0);
expect(cutStitch.isCut).toBe(true); expect(colorEndCutStitch.isCut).toBe(true);
expect(colorEndCutStitch.isColorEnd).toBe(true);
idx++; idx++;
// 5. COLOR_END marker (no jump needed since same position) // 5. 8 starting lock stitches for new color
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
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx]; const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
@ -320,7 +314,7 @@ describe('encodeStitchesToPen', () => {
idx++; idx++;
} }
// 7. First stitch of new color // 6. First stitch of new color
expect(decoded[idx].x).toBe(10); expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0); expect(decoded[idx].y).toBe(0);
idx++; idx++;
@ -353,9 +347,11 @@ describe('encodeStitchesToPen', () => {
idx++; idx++;
} }
// 2. Cut command at (10, 0) // 2. COLOR_END + CUT command at (10, 0) - combined on same stitch
expect(decoded[idx].isCut).toBe(true);
expect(decoded[idx].x).toBe(10); 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++; idx++;
// 3. Jump to new position (30, 10) // 3. Jump to new position (30, 10)
@ -364,20 +360,14 @@ describe('encodeStitchesToPen', () => {
expect(decoded[idx].isFeed).toBe(true); expect(decoded[idx].isFeed).toBe(true);
idx++; idx++;
// 4. COLOR_END marker at (30, 10) // 4. 8 starting lock stitches 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)
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
idx++; idx++;
} }
// 6. Continue with new color stitches // 5. Continue with new color stitches
expect(decoded[idx].x).toBe(30); expect(decoded[idx].x).toBe(30);
expect(decoded[idx].y).toBe(10); expect(decoded[idx].y).toBe(10);
}); });
@ -421,10 +411,11 @@ describe('encodeStitchesToPen', () => {
idx++; 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].x).toBe(10);
expect(decoded[idx].y).toBe(0); expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isCut).toBe(true); expect(decoded[idx].isCut).toBe(true);
expect(decoded[idx].isColorEnd).toBe(true);
idx++; idx++;
// 5. Jump to new location (50, 20) - extracted from the MOVE stitch // 5. Jump to new location (50, 20) - extracted from the MOVE stitch
@ -433,26 +424,20 @@ describe('encodeStitchesToPen', () => {
expect(decoded[idx].isFeed).toBe(true); expect(decoded[idx].isFeed).toBe(true);
idx++; idx++;
// 6. COLOR_END marker at (50, 20) // 6. 8 starting lock stitches 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)
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE); expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
idx++; 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].x).toBe(50);
expect(decoded[idx].y).toBe(20); expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isFeed).toBe(false); expect(decoded[idx].isFeed).toBe(false);
idx++; idx++;
// 9. Last stitch with DATA_END // 8. Last stitch with DATA_END
expect(decoded[idx].x).toBe(60); expect(decoded[idx].x).toBe(60);
expect(decoded[idx].y).toBe(20); expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isDataEnd).toBe(true); expect(decoded[idx].isDataEnd).toBe(true);
@ -666,7 +651,7 @@ describe('encodeStitchesToPen', () => {
const result = encodeStitchesToPen(stitches); const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes); const decoded = decodeAllPenStitches(result.penBytes);
console.log(decoded);
// Expected sequence: // Expected sequence:
// 0. Feed move to proper location (should always happen here) // 0. Feed move to proper location (should always happen here)
// 1. 8 starting lock stitches at (10, 20) // 1. 8 starting lock stitches at (10, 20)

View file

@ -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 FEED_LENGTH = 50; // Long jump threshold requiring lock stitches and cut
const TARGET_LENGTH = 8.0; // Target accumulated length for lock stitch direction const TARGET_LENGTH = 8.0; // Target accumulated length for lock stitch direction
const MAX_POINTS = 5; // Maximum points to accumulate 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 const LOCK_STITCH_SCALE = LOCK_STITCH_JUMP_SIZE / 8.0; // Scale the magnitude-8 vector down to 4
export interface StitchData { export interface StitchData {
@ -286,7 +286,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
penStitches.push(...generateLockStitches(absX, absY, startDir.dirX, startDir.dirY)); 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) { if (isColorChange) {
const nextStitchCmd = nextStitch[2]; const nextStitchCmd = nextStitch[2];
const nextStitchX = Math.round(nextStitch[0]); const nextStitchX = Math.round(nextStitch[0]);
@ -299,17 +299,26 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
const finishDir = calculateLockDirection(stitches, i, true); const finishDir = calculateLockDirection(stitches, i, true);
penStitches.push(...generateLockStitches(absX, absY, finishDir.dirX, finishDir.dirY)); penStitches.push(...generateLockStitches(absX, absY, finishDir.dirX, finishDir.dirY));
// Step 2: Add cut command at current position // Step 2: Add COLOR_END + CUT command at CURRENT position (same stitch!)
const cutXEncoded = (absX << 3) & 0xffff; // This is where the machine pauses and waits for the user to change thread color
const cutYEncoded = ((absY << 3) & 0xffff) | PEN_CUT_DATA; // 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( penStitches.push(
cutXEncoded & 0xff, colorEndCutXEncoded & 0xff,
(cutXEncoded >> 8) & 0xff, (colorEndCutXEncoded >> 8) & 0xff,
cutYEncoded & 0xff, colorEndCutYEncoded & 0xff,
(cutYEncoded >> 8) & 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 // Step 3: If next stitch is a JUMP, encode it and skip it in the loop
// Otherwise, add a jump ourselves if positions differ // Otherwise, add a jump ourselves if positions differ
const jumpToX = nextStitchX; const jumpToX = nextStitchX;
@ -334,22 +343,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
); );
} }
// Step 4: Add COLOR_END marker at NEW position // Step 4: Add starting lock stitches at the 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
// Loop A: Jump/Entry Vector - Look FORWARD at upcoming stitches in new color // 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 // This hides the knot under the stitches we're about to make
const nextStitchIdx = nextIsJump ? i + 2 : i + 1; const nextStitchIdx = nextIsJump ? i + 2 : i + 1;