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