Merge pull request #13 from jhbruhn/fix/combine-color-end-cut-flags

fix: Combine COLOR_END and CUT flags on same stitch for color changes
This commit is contained in:
Jan-Henrik Bruhn 2025-12-14 16:52:14 +01:00 committed by GitHub
commit 254b09271e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 60 deletions

View file

@ -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)

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 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;